内存分配器(Memory Allocator)负责内存管理,实现动态内存的分配和释放。内存分配器分为两级。第一级分配器直接调用C函数分配内存,第二级分配器则采用内存池来管理内存。如果申请的内存块足够大,那么启动第一级分配器,否则启动第二级分配器。这种设计的优点是可以快速分配和释放小块内存,同时避免内存碎片;缺点是内存池的生命周期比较长,并且很难显式释放。
一些平台对某些特定类型的数据只能从某些特定地址开始存取,这就要求内存分配器可以由使用者指定对齐字节数。在通常情况下,考虑数据类型bool、char、short、int、long long、float、double的最大数据长度为64bit,可以采用8字节对齐,这也是内存分配器的默认对齐参数。但是,使用__m128、__m128i、__m128d时需要16字节对齐,使用__m256则需要32字节对齐。
第一级分配器只是简单的调用函数malloc()、realloc()和free()。为了保证内存按照指定字节数对齐,则需要调用函数_aligned_malloc()、_aligned_realloc()和_aligned_free(),因此实际分配的内存块可能大于申请内存的大小。
第二级分配器需要维护16个空闲块链表和一个内存池。每个链表中的空闲块的大小都是固定的,假定对齐字节数为n,则各个链码空闲块大小依次为n、2n、3n、4n、5n、6n、7n、8n、9n、10n、11n、12n、13n、14n、15n、16n。内存池由两个指针来描述,free_start记录起始地址,free_end记录结束地址。另外两个变量heap_size和used_size分别纪录堆大小和已用内存大小。
![](https://img-blog.csdn.net/20150814151545999)
内存池管理的内存块大小只有固定的16个规格, 当所需内存块大于16n时,则使用第一级分配器进行内存分配。否则,按照以下步骤进行内存分配:
-
申请内存的大小上调至n的倍数,根据此大小查找对应的空闲链表;
-
如果空闲链表中有可用的内存块,则直接返回此空闲块,并从空闲链表中删除该块,否则继续下面的步骤;
-
计算内存池中所剩空间的大小,如果足以分配16个内存块,则从中取出16个内存块,调整内存池起始地址,返回第一个内存块,并将剩余的15个块并入空闲链表,否则继续下面的步骤;
-
如果剩余空间足以分配至少1个内存块,则从中取出尽可能多的内存块,调整内存池起始地址,返回第一个内存块,并将剩余的内存块并入空闲链表,否则继续下面的步骤;
-
如果内存池中还有一些内存,则将剩余空间并入其对应大小的空闲链表中;
-
向系统申请一个较大的内存块,如果申请成功,返回第一个内存块,调整内存池起始地址,否则继续下面的步骤;
-
遍历空闲链表,如果存在更大的空闲内存块,则从空闲链表中删除该块,返回该块首地址,并将剩余的部分内存交给内存池管理,否则分配失败。
内存池向系统申请的内存空间,在使用过程中会被划分为更小的内存块,而这些小内存块的使用和归还几乎是随机的。如果试图对这些小内存块进行合并和释放,其高昂的代价会大幅降低内存池的性能。但在内存池的已用内存大小为0时,释放内存是安全的。内存分配器维护一个指针链表,用于内存空间的统一释放。
内存分配器的对齐字节数决定了空闲链表中的内存块大小,这就意味着对齐字节数不同的分配器所维护的空闲链表是不相同的。因此,对齐字节数相同的分配器被认为是同一个分配器,否则被认为是不同的分配器。这与C++标准中对内存分配器的规定是不一样的,标准中的内存分配器没有考虑内存对齐,所有分配器都被视为相等。这也是C++标准容器不支持有内存对齐要求的数据类型的原因,如:__m128、__m128i、__m128d和__m256等数据。
- template <size_t align>
- class alloc_base
- {
- private:
-
- union free_node
- {
- union free_node* next;
- char buffer[1];
- };
-
- struct ptr_node
- {
- ptr_node* next;
- char* ptr;
- };
- private:
- static const size_t max_bytes = align << 4;
- static const size_t list_count = 16;
- static const size_t chunk_count = 16;
- static const size_t heap_threshold = (max_bytes * chunk_count) << 5;
- static const size_t ptr_node_size = (sizeof(ptr_node) + align - 1) & ~(align - 1);
-
- static free_node* volatile free_list[list_count];
- static ptr_node* volatile ptr_list;
- static char* free_start;
- static char* free_end;
- static size_t heap_size;
- static size_t used_size;
- private:
-
- static size_t round_up(size_t bytes)
- {
- return ((bytes + align - 1) & ~(align - 1));
- }
-
- static size_t free_list_index(size_t bytes)
- {
- return (bytes + align - 1) / align - 1;
- }
-
- static char* chunk_alloc(size_t align_bytes, size_t& count)
- {
- char* result = nullptr;
- size_t memory_size;
- size_t total_bytes = align_bytes * count;
-
-
- if (heap_size < heap_threshold)
- memory_size = total_bytes << 1;
- else
- memory_size = (heap_size >> 7) << 3;
- while (memory_size >= total_bytes)
- {
-
- char* ptr = reinterpret_cast<char*> (_aligned_malloc(ptr_node_size + memory_size, align));
- if (ptr != nullptr)
- {
- result = ptr + ptr_node_size;
- free_start = result + total_bytes;
- free_end = result + memory_size;
- heap_size += memory_size;
-
- reinterpret_cast<ptr_node*> (ptr)->next = ptr_list;
- reinterpret_cast<ptr_node*> (ptr)->ptr = ptr;
- ptr_list = reinterpret_cast<ptr_node*> (ptr);
- break;
- }
-
- memory_size >>= 1;
- }
-
- if (result == nullptr)
- {
-
- for (size_t size = align_bytes; size <= max_bytes; size += align)
- {
- free_node* volatile* current_list = free_list + free_list_index(size);
- if (*current_list != nullptr)
- {
- count = 1;
- result = (*current_list)->buffer;
- free_start = result + align_bytes;
- free_end = result + size;
- break;
- }
- }
- }
- return result;
- }
- public:
-
- static size_t capacity(void)
- {
- return heap_size;
- }
-
-
- static size_t size(void)
- {
- return used_size;
- }
-
-
- static void* allocate(size_t size)
- {
-
- if (size > max_bytes)
- {
- void* result = _aligned_malloc(size, align);
- if (result == nullptr)
- throw std::bad_alloc();
- return result;
- }
-
- else
- {
- char *result = nullptr;
- size_t count = chunk_count;
- free_node* volatile* current_list = free_list + free_list_index(size);
-
-
- if (*current_list != nullptr)
- {
- result = (*current_list)->buffer;
- *current_list = (*current_list)->next;
- }
-
- else
- {
- size_t align_bytes = round_up(size);
- size_t total_bytes = align_bytes * count;
- size_t free_bytes = free_end - free_start;
-
- if (free_bytes >= total_bytes)
- {
- result = free_start;
- free_start += total_bytes;
- }
- else if (free_bytes >= align_bytes)
- {
- count = free_bytes / align_bytes;
- total_bytes = align_bytes * count;
- result = free_start;
- free_start += total_bytes;
- }
- else
- {
-
- if (free_bytes > 0)
- {
- free_node* volatile* free_list_left = free_list + free_list_index(free_bytes);
- reinterpret_cast<free_node*> (free_start)->next = *free_list_left;
- *free_list_left = reinterpret_cast<free_node*> (free_start);
- free_start = free_end;
- }
-
- result = chunk_alloc(align_bytes, count);
- }
-
- if (result != nullptr && count > 1)
- {
- char *cur, *next = result + align_bytes;
- *current_list = reinterpret_cast<free_node*> (next);
- for (size_t i = 2; i < count; ++i)
- {
- cur = next;
- next += align_bytes;
- reinterpret_cast<free_node*> (cur)->next = reinterpret_cast<free_node*> (next);
- }
- reinterpret_cast<free_node*> (next)->next = nullptr;
- }
- }
- if (result != nullptr)
- used_size += size;
- else
- throw std::bad_alloc();
- return reinterpret_cast<void*> (result);
- }
- }
-
-
- static void deallocate(void* ptr, size_t size)
- {
- if (size > max_bytes)
- {
- _aligned_free(ptr);
- }
- else
- {
- free_node* volatile* current_list = free_list + free_list_index(size);
- reinterpret_cast<free_node*> (ptr)->next = *current_list;
- *current_list = reinterpret_cast<free_node*> (ptr);
- used_size -= size;
- }
- }
-
-
- static void* reallocate(void *ptr, size_t old_size, size_t new_size)
- {
- void *result = nullptr;
- size_t copy_size;
-
- if (old_size > max_bytes && new_size > max_bytes)
- {
- result = _aligned_realloc(ptr, new_size, align);
- }
- if (round_up(old_size) == round_up(new_size))
- {
- result = ptr;
- used_size -= old_size;
- used_size += new_size;
- }
- else
- {
- result = allocate(new_size);
- if (result == nullptr)
- {
- copy_size = new_size > old_size ? old_size : new_size;
- memcpy(result, ptr, copy_size);
- deallocate(ptr, old_size);
- }
- }
- if (result == nullptr)
- throw std::bad_alloc();
- return result;
- }
-
-
- static void release(void)
- {
- if (used_size != 0)
- return;
- while (ptr_list != nullptr)
- {
- ptr_node *next = ptr_list->next;
- _aligned_free(ptr_list->ptr);
- ptr_list = next;
- }
- heap_size = 0;
- used_size = 0;
- }
- };