springboot page size过大导致内存溢出_高性能内存分配库 mimalloc 简介

本文介绍了mimalloc,一个由微软开源的内存分配库,具有高性能和小体积的特点。mimalloc通过空闲列表分片、优化的分配和释放机制,以及无锁的多线程数据竞争解决方案,提高了内存分配效率。文章详细阐述了其内存分配和释放机制,包括空闲列表分片、本地空闲列表和线程释放列表的概念,以及malloc和free的具体实现。
摘要由CSDN通过智能技术生成

一、简介

mimalloc 是微软研究院在 2019 年发表并开源的一个新的内存分配库:

  • 代码量少,核心代码行数 <3500 行

    • tcmalloc ~20k LOC

    • jemalloc ~25k LOC

  • 性能大大优于市面上其他 memory allocator

    • 比 tcmalloc 快 7%

    • 比 jemalloc 快 14%

  • 三个局部存储的分片的空闲列表

    • 增加数据访问局部性 (locality)

    • 减少线程访问竞争

    • 支持性能优化到极致的分配和释放的 fast path

    • 引入一个时间节奏(temporal cadence),使分配器适时离开 fast path 来处理一些维护性的任务

二、内存分配和释放机制

mimalloc 的内存分配释放机制要点:

  • 极致的空闲列表分片

  • 分配和释放的主路径经过深度优化,其他的情况都被延迟到 generic 方法中进行。

  • 没有使用锁,所有的多线程数据竞争都使用原子操作来解决。最坏情况下有上界,元数据约占 0.2%,实际分配空间浪费不超过 16.7%。

1. 空闲列表分片机制

  • 空闲列表

    • 为每个 mimalloc 页(一般是64KiB)维护一个空闲列表,而不是像传统机制那样为整个 size class(如 2^n)维护一个空闲列表。

    • 只要当前页的空闲列表不为空,malloc 就可以一直分配本页中的内存,而不用管堆中是不是有其他新 free 的区域

    • 这样可以提高新分配内存的空间局部性,使大部分新分配的地址都处在同一页中,提高 L2 Cache 的命中率

e0e8242d11b9d890af92d92a5653ccb1.png

本地空闲列表

  • 释放大对象时,可能会引起连锁反应递归地释放一大堆对象,造成最坏情况下的 free 时长不可控。因此需要限制 free 的个数,将后续要 free 的对象挂在一个列表上,等后续内存紧张时再释放(malloc_generic 中)。但需要一个机制保证 malloc_generic 每隔一段时间就能被调用到。

  • 为每个 mimalloc 页除了维护一个空闲列表外,再增加一个本地空闲列表(local-free list),当空闲列表被分配完后,将调用 malloc_generic,malloc_generic 会将本地空闲列表转成新的空闲列表。

    page->free = page->local_free; // move the listpage->local_free = NULL; // and the local list is empty again
  • 因为空闲列表不会增长,经过一段时间总会被分配完,此机制相当于引入一个时间节奏,使 malloc_generic 的分配代码总是能够定期地被执行,此机制可以用来平摊一些耗时操作的开销:

    • 延后执行的引用计数减导致的 free

    • 维持一个稳定的心跳机制

    • 回收线程释放列表中的内存

线程释放列表

  • 在 mimalloc 中,mimalloc 页属于一个“线程局部”的堆,所有本线程的内存分配都从这个线程局部堆上分配,不需要锁。

  • 但任何线程都能 free 本线程分配的内存。为保证本线程 free 本线程局部堆上的数据不需要锁,将其他线程释放的内存放到一个独立的”线程释放列表“中,也能减少 malloc 中 fast path 上的原子操作。

    • 其他线程释放本线程分配的空间时,会调用 atomic_push 方法,将对应的地址原子地放到本线程的释放列表中:

void atomic_push( block_t** list, block_t* block ) {  do { block->next = *list; }  while (!atomic_compare_and_swap(list, block, block->next));}

各个线程释放内存时操作的对象被分片到各个 mimalloc 页上,减少了线程间的竞争

每隔一段时间线程释放列表会被原子地移动到空闲列表中,达到批量处理远程 free 的效果

tfree = atomic_swap( &page->thread_free, NULL );append( page->free, tfree );

2. 具体实现

  • malloc 实现

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;}

5d4a022927fbc8a930c35baf43152161.png

//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;       // 本地空闲列表置空    ... // 原子地处理 线程释放 列表}
  • 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 );  }}

参考资料

  • Mimalloc 论文:https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action/

  • Mimalloc 源码:https://github.com/microsoft/mimalloc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值