1. 常见的内存管理模式
Per-class allocators
常见的mempool就是这种实现,pool是一块大内存,分割成一个个的固定大小的对象。polarstore采用的内存管理就是这种方式,不同的对象归属到不同的mempool。
这种方式的优点是实现非常简单,性能也相当好,缺点是内存消耗的放大很严重。Regions
Imci的Arena以及innodb的mem_heap_t就是这类实现,提前分配一整块内存,分配对象时,只需要指针在内存上移动。
这种实现是所有实现里性能最好的,缺点是只有malloc跟freeall的接口,没办法free一个特定的对象,长时间运行的任务,内存无法及时释放,导致系统内存水位高。MySQL经常出现slow log导致的OOM,就是regions的负作用。
General-purpose allocator
tcmalloc, jemalloc,以及libc自带的ptmalloc都归为这类,与上面两类最大的区别就是支持variable length的malloc/free,以及支持free任意一块内存。
显然,对于PolarDB IMCI来讲,Per-class 以及Regions都有难以克服的缺陷。我们不可能对每个类型的class分配一个mempool。数据分析场景一条sql跑几十分钟不罕见,期间一直不释放内存,其他短SQL很容易分配不出内存。
因此,我们只考虑General-purpose的实现,既要speed,也要low consumption。
通过对两款典型的General-purpose allocalor:mimalloc以及jemalloc的分析,一方面调研能否直接引入为IMCI所用,另一方面如何吸收优秀的设计,简化无用的实现。
mimalloc比较新,2019年推出,性能数字惊艳,吊打jemalloc,核心代码3500行。但缺乏large buffer的优化。
调研文档:LINK
jemalloc有facebook背书,配套工具完善,mariadb引入jemalloc做为默认的分配器,aliyun rds随后跟进,PolarDB也一直沿用下来。jemalloc风评好,ptmalloc被各种嫌弃,我个人的看法是,业内普遍在用古早版本的glibc,新版本scalibility方面的大量工作缺乏宣传。根据2019年微软研究院的测试结果https://www.microsoft.com/en-us/research/uploads/prod/2019/06/mimalloc-tr-v1.pdf,jemalloc在benchmark场景表现惊艳,真实场景如redis,表现不如glibc。
调研文档:(TODO)
2. 针对应用定制的Allocator
相当一部分系统软件会自己定制内存分配器。定制allocator最大的好处可以聚焦应用本身的common case,极大降低复杂度。针对系统使用内存的模式,有具体场景更容易针对性的优化,性能上也不难取得优势。
以MySQL为例,THD本身是query arena的子类,基类重载了new/delete,同query arena预先分配page,allocate时在page上切割内存,THD析构时deallocate,最大的好处是统一了生命周期,杜绝掉内存泄露的可能性,内存分配的性能是所有其他方案的天花板。负作用是defer freeing会导致内存水位高。
MySQL存储引擎的page由buffer pool管理,其他对象也支持从buffer pool分配,支持不超过page size大小的分配,实例启动时mmap好所有内存,第一次写入时映射好物理页,实例退出时munmap。page 淘汰后,归还freelist,几乎不依赖虚拟内存,自然不会有缺页中断。通过buffer pool绕过了page cache以及kernel buddy system,我认为是MySQL点查询性能压过PG的主要原因。
另一款AP数仓Impala,早期版本依赖tcmalloc,被free性能折磨后
Reduced reliance on TCMalloc, which isn't suited to management of large buffers (e.g. see IMPALA-2800)
2.0版本选择自己开发buffer pool (https://issues.apache.org/jira/browse/IMPALA-3200?jql=project%20%3D%20IMPALA%20AND%20text%20~%20%22buffer%20manage%22)。
其特点是执行层,存储的所有内存都从buffer pool分配,支持variable-length。除了profile,statistic,safety等功能外,比较亮眼的是支持了spilling。(https://issues.apache.org/jira/browse/IMPALA-3202)。
Custom allocator除了实现简单以及性能优势外,还有很重要的一点是可以根据应用的需求定制,impala可以在内存高水位(over 80% limit)时,后台线程定时把超过水位的memory归还给OS。一方面尽量延迟free内存来保证性能,另一方面,防止内存水位过高,引起用户焦虑。
不同的系统内存使用的性能瓶颈也不一样,以IMCI为例,多个客户的场景,瓶颈都是big buffer的处理上,通用的内存分配器认为big buffer是很少reuse的,因此释放很激进。
gnu libc的allocator ptmalloc,超过一定大小的内存,直接通过mmap跟os申请,free后直接返还os。
jemalloc认为large memory(超过4MB)是less reuse的,为了减少碎片,large memory的分配走单独的arena,为了降低内存水位,延迟释放等优化都会忽略掉large memory's dedicated arena。
总结一下,imci依赖jemalloc带来的几个问题是
dedicated arena影响了scalibility。
purge eargly,导致频繁的munmap。
结果是系统对大表或宽表大量扫描时,通常频繁分配large buffer,必然导致并发能力变差,大量出现slow log。
易仓/advance都是jemalloc的victim。




