1、简介
mimalloc
是微软研究院在2019年发表并开源的一个新的内存分配库(github.com/microsoft/m…
尝试可以先clone到本地:
git clone https://github.com/microsoft/mimalloc.git
第二步可以先编译:
cmake ../mimalloc/ && make && make install
第三步可以将头文件拷贝到系统目录,如:
cp -r ./mimalloc/include/* /usr/include/
最后编译代码的时候带上 mimalloc
:
gcc -o test ./test.c -lmimalloc
如果不编译直接在程序中使用可以尝试,如下:
LD_PRELOAD=/usr/lib/libmimalloc.so myprogram
环境变量选项:
- MIMALLOC_SHOW_STATS=1: 程序运行时统计内存使用信息
- MIMALLOC_VERBOSE=1: 显示debug日志
- MIMALLOC_SHOW_ERRORS=1: 显示error和warning信息
- MIMALLOC_PURGE_DELAY=N: 对于不再使用的内存,延时Nms在回收(默认10)
- MIMALLOC_ARENA_EAGER_COMMIT=1: 对于申请大内存时(1GiB),打开立即向OS申请内存开关
- MIMALLOC_USE_NUMA_NODES=N: 设置最多有N个NUMA节点,如果未设置,那么在实际运行时检测NUMA节点
2、对比其他的内存库
(1)代码量少,核心代码行数 < 3500 行
- tcmalloc ~20k LOC
- jemalloc ~25k LOC
(2)性能大大优于市面上其他 memory allocator
- 比 tcmalloc 快 7%
- 比 jemalloc 快 14%
(其中mi:mimalloc,tc:tcmalloc,je:jemalloc ...)
(3)多线程设计
- mimalloc是专门为多线程应用设计的一款高性能的内存分配器,能极大降低多线程环境下线程间同步带来的性能下降,与tcmalloc类似,mimalloc采用了线程本地缓存的设计,同时采用了复杂的缓存管理机制提升内存分配和释放的速度;
- mimalloc的内存分配/释放算法是lock-free的,意味着它是async-signal-safe;
3、架构
架构
在mimalloc中,每个线程都有一个Thread Local的堆,每个线程在进行内存的分配时均从该线程对应的堆上进行分配,在一个堆中会有一个或多个segment,一个segment会对应一个或多个page,而内存的分配就是在这些page上进行。
- tlb:大多数高性能内存分配器产品都会实现自己的内存池(缓存),以减少系统调用(mmap)的次数,
mimalloc
将缓存进一步划分到各个线程中,各线程的内存分配都走线程本地缓存(tld),最大程度避免了内存分配时线程间的(锁)竞争; - heap:一级缓存,是分配内存时的第一搜索位置,其内部使用了相当巧妙的结构和算法来组织和管理内存块,以实现内存块的快速索引和改善连续内存分配时的局域性;
- segment:面向
heap
的二级缓存,是通过mmap申请的连续大内存区域(32MB),起到了缓冲heap频繁调整大小的作用; - page:
page
是mimalloc
中内存管理的最小单位,它由大小固定block序列和元数据组成,这些block由隶属于page的free list
组织,当进行内存申请时,最终返回的就是free list
中第一个block; - free list:上层应用请求分配内存时block的直接来源,大部分时间直接
pop-from-head
即可,只有在变空时才从另外两条free list
补充block; - local-free list:上层应用(同一线程)释放内存时block的直接归处,大部分时间直接
push-to-head
即可; - thread-free list :上层应用(跨线程)释放内存时block的直接归处,通过
lock-free
算法实现push-to-head
即可;
其中对 free list
操作的代码:
...
page->free = page->local_free; // move the list
page->local_free = NULL; // and the local list is empty again
...
void atomic_push( block_t** list, block_t* block ) {
do { block->next = *list; }
while (!atomic_compare_and_swap(list, block, block->next));
}
...
tfree = atomic_swap( &page->thread_free, NULL );
append( page->free, tfree );
4、内存申请和释放源码
(1)malloc实现
extern inline void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept {
void* p;
if (mi_likely(size <= MI_SMALL_SIZE_MAX)) {
p = mi_heap_malloc_small(heap, size);
}
else {
p = _mi_malloc_generic(heap, size);
}
return p;
}
void* malloc_small( size_t n ) { // 0 < n <= 1024
heap_t* heap = tlb; //线程局部存储指针指向的线程局部堆
page_t* page = heap->pages_direct[(n+7)>>3]; // divide up by 8
block_t* block = page->free; //空闲列表
if (block==NULL) return malloc_generic(heap,n); // slow path
page->free = block->next; //移动空闲列表指针
page->used++;
return block;
}
//slow path,mimalloc机制保证此方法会隔一段时间被调到
void* malloc_generic( heap_t* heap, size_t size ) {
deferred_free(); // 调用用户自定义的方法
// 遍历现有page
foreach( page in heap->pages[size_class(size)] ) {
page_collect(page); //回收页内空间
if (page->used - page->thread_freed == 0) {
page_free(page);
}
else if (page->free != NULL) {
return malloc(size);
}
}
.... // 现有页中无空闲空间,重新分配一页,并从新页中分配空间
}
void page_collect(page) {
page->free = page->local_free; // 将空闲列表设置为本地空闲列表
page->local_free = NULL; // 本地空闲列表置空
... // 原子地处理 线程释放 列表
}
(2)free实现
void free( void* p ) {
//找到p所在的segment
segment_t* segment = (segment_t*)((uintptr_t)p & ~(4*MB));
if (segment==NULL) return;
//找到p所在的page
page_t* page = &segment->pages[(p - segment) >> segment->page_shift];
block_t* block = (block_t*)p;
if (thread_id() == segment->thread_id) { // free的是本线程分配的内存(local free)
block->next = page->local_free;
page->local_free = block;
page->used--;
if (page->used - page->thread_freed == 0) page_free(page);
}
else { // free的是其他线程分配的内存(non-local free)
atomic_push( &page->thread_free, block);
atomic_incr( &page->thread_freed );
}
}
参考
[1] www.cnblogs.com/linkwk7/p/1…
[2] github.com/microsoft/m…
[3] www.cnblogs.com/linkwk7/p/1…