MySQL内存分配详解(二)

InnoDB存储引擎层基础内存分配

如果在编译MySQL的时候不开启FPS的监控,InnoDB对动态内存(heap)的分配和释放使用基础的new、delete、malloc、free等。

默认InnoDB对内存的分配和回收会添加FPS的监控模块。InnoDB对动态内存(heap)的分配和回收使用封装后的函数,主要在ut_allocator类中实现,具体内存的分配和回收方法有以下几种:

  • 单块内存分配函数:allocate
  • 单块内存回收函数:deallocate
  • 内存重新分配函数:reallocate
  • Array对象内存分配函数:new_array
  • Array对象内存回收函数:delete_array
  • 大块内存分配函数:allocate_large
  • 大块内存回收函数:deallocate_large
  • 内存分配跟踪函数:allocate_trace
  • 内存回收跟踪函数:deallocate_trace

其中还需要两个重要的数据对象ut_new_pfx_t和PSI_memory_key。

PSI_memory_key指的是Performance schema key,标识了调用PFS接口进行内存分配或回收监控时的事件名称(即event_name),以下为一些非默认的事件名称,如果没有指定事件名称,则使用默认的event_name(比如memory/innodb/dict0stats、memory/innodb/std):

ut_new_pfx_t结构中包含了三个属性,分别是:

  • PSI_memory_key m_key
  • struct PSI_thread *m_owner:内存操作所属的线程
  • size_t m_size:分配内存的大小(以字节为单位),包括pfx结构本身占用的内存

下面具体看一下“单块内存分配函数:allocate”的实现逻辑,allocate函数主要用于容器内部对象的内存分配,包括上面讲的“Array对象内存分配函数:new_array”也是调用allocate进行的内存分配。具体代码如下(省略部分代码,只截取关键语句):

  pointer allocate(size_type n_elements, const_pointer hint = nullptr,
                   PSI_memory_key key = PSI_NOT_INSTRUMENTED,
                   bool set_to_zero = false, bool throw_on_error = true) {
void *ptr;
// 统计待分配对象占用内存大小
size_t total_bytes = n_elements * sizeof(T);
// 总共需要的内存大小(包括pfx对象)
total_bytes += sizeof(ut_new_pfx_t);
// 进行内存分配,如果分配失败,进行重试操作
    for (size_t retries = 1;; retries++) {
      if (set_to_zero) {
        ptr = calloc(1, total_bytes);
      } else {
        // 调用标准的malloc函数进行内存分配
        ptr = malloc(total_bytes);
      }
      if (ptr != nullptr || retries >= alloc_max_retries) {
        break;
      }
      std::this_thread::sleep_for(std::chrono::seconds(1));
}

// 如果达到最大失败次数,则抛出异常,相关代码省略

// 初始化pfx对象信息
ut_new_pfx_t *pfx = static_cast<ut_new_pfx_t *>(ptr);
// 跟踪内存的分配
    allocate_trace(total_bytes, key, pfx);
    return (reinterpret_cast<pointer>(pfx + 1));
  }

下面具体看一下“单块内存回收函数:deallocate”的具体实现:

  void deallocate(pointer ptr, size_type n_elements = 0) {
    // 内存回收时跟踪内存的分配并调用标准free函数进行内存回收
    ut_new_pfx_t *pfx = reinterpret_cast<ut_new_pfx_t *>(ptr) - 1;
    deallocate_trace(pfx);
    free(pfx);
  }

下面看一下“大块内存分配函数:allocate_large”(比如用于Buffer Pool内存的resize)的具体实现(省略部分代码):

  pointer allocate_large(size_type n_elements, ut_new_pfx_t *pfx) {
ulint n_bytes = n_elements * sizeof(T);
/* 
large page的内存分配主要是通过os_mem_alloc_large
函数进行mmap系统调用直接分配内存,并进行内存跟踪
    */
pointer ptr = reinterpret_cast<pointer>(os_mem_alloc_large(&n_bytes));
    if (ptr != nullptr) {
      allocate_trace(n_bytes, PSI_NOT_INSTRUMENTED, pfx);
    }
    return (ptr);
  }

而“大块内存回收函数:deallocate_large”则主要是通过os_mem_free_large函数进行munmap系统调用进行内存释放并进行内存跟踪监控:

  void deallocate_large(pointer ptr, const ut_new_pfx_t *pfx) {
    deallocate_trace(pfx);
    os_mem_free_large(ptr, pfx->m_size);
  }

内存的分配或回收的跟踪监控主要是通过调用PSI监控接口来实现。

内存分配监控跟踪:

  void allocate_trace(size_t size, PSI_memory_key key, ut_new_pfx_t *pfx) {
    if (m_key != PSI_NOT_INSTRUMENTED) {
      key = m_key;
    }
    pfx->m_key = PSI_MEMORY_CALL(memory_alloc)(key, size, &pfx->m_owner);
    pfx->m_size = size;
  }

内存回收监控跟踪:

  void deallocate_trace(const ut_new_pfx_t *pfx) {
    PSI_MEMORY_CALL(memory_free)(pfx->m_key, pfx->m_size, pfx->m_owner);
  }

而实际进行内存分配或回收时主要是通过一些宏或容器对象来进行的,比如UT_NEW(expression, key)、UT_NEW_ARRAY(type, num, key)、UT_DELETE_ARRAY(ptr)、std::vector<t, ut_allocator<t> > v(ut_allocator<t>(key))、ut_malloc、ut_free等,这些宏在进行内存分配的时候都指定了PSI_memory_key,还有一些宏使用默认的key,如UT_NEW_NOKEY(expression)、UT_NEW_ARRAY_NOKEY(type, num)、 std::vector<t, ut_allocator<t> > v等。

Server层基础内存分配

Server层内存的基础分配和回收函数主要是以下几种:

下面具体看一下my_malloc的代码实现:

/* 
my_malloc函数主要是通过调用my_raw_malloc函数来进行内存分配;并直接调用PSI接口进行内存跟踪
*/
void *my_malloc(PSI_memory_key key, size_t size, myf flags) {
  my_memory_header *mh;
  size_t raw_size;
  raw_size = HEADER_SIZE + size;
  mh = (my_memory_header *)my_raw_malloc(raw_size, flags);
  if (likely(mh != nullptr)) {
    void *user_ptr;
    mh->m_magic = MAGIC;
    mh->m_size = size;
    mh->m_key = PSI_MEMORY_CALL(memory_alloc)(key, size, &mh->m_owner);
    user_ptr = HEADER_TO_USER(mh);
    MEM_MALLOCLIKE_BLOCK(user_ptr, size, 0, (flags & MY_ZEROFILL));
    return user_ptr;
  }
  return nullptr;
}
// 而my_raw_malloc函数的实现主要是通过malloc标准函数进行内存分配(省略部分代码)
static void *my_raw_malloc(size_t size, myf my_flags) {
  void *point;
  /* Safety */
  if (!size) size = 1;
  if (my_flags & MY_ZEROFILL)
    point = calloc(size, 1);
  else
    point = malloc(size);
    point = nullptr;
  });
  return (point);
}

下面看一下my_free的代码实现:

// my_free的实现逻辑主要是通过调用my_raw_free函数实现,并直接调用PSI接口进行内存跟踪
void my_free(void *ptr) {
  my_memory_header *mh;
  if (ptr == nullptr) return;
  mh = USER_TO_HEADER(ptr);
  assert(mh->m_magic == MAGIC);
  PSI_MEMORY_CALL(memory_free)(mh->m_key, mh->m_size, mh->m_owner);
  /* Catch double free */
  mh->m_magic = 0xDEAD;
  MEM_FREELIKE_BLOCK(ptr, 0);
  my_raw_free(mh);
}

// my_raw_free则使用标准free函数进行内存释放
static void my_raw_free(void *ptr) {
  free(ptr);
}

从以上内存分配相关代码可知:内存分配的标准函数主要涉及new、malloc、mmap。

posted @ 2023-08-02 17:06  吃饭端住碗  阅读(45)  评论(0编辑  收藏  举报