一、项目概述
1.简介
基于Google开源tcmalloc项目,该内存池主要应用于多线程频繁申请和释放大量小块内存。它的优点:性能卓越、大幅减少了外内存碎片。
2.工具
Google的tcmalloc开源代码,使用vs2019进行研究学习。
3.目标
- 建立一个可行的私人高并发内存池,需要解决以下三个问题:
- 效率问题
- 内存碎片问题
- 多线程并发场景下的内存释放和申请的锁竞争问题。
- 深入理解内存管理
4.扩展
- 完全脱离malloc和free,且不要使用thread和mutex
- 处理不支持本地线程存储(TLS)的跨平台问题
- 将静态TLS更新为动态TLS
CentralCache和page cache目前是一个全局对象,更新为单例模式- 接入STL库,使之正常使用
二、项目背景
1.直接使用new/delete、malloc/free存在的问题
new/delete用于c++中动态内存管理而malloc/free在c++和c中都可以使用,本质上new/delete底层封装了malloc/free。无论是上面的那种内存管理方式,都存在以下两个问题:
- 效率问题:频繁的在堆上申请和释放内存必然需要大量时间,降低了程序的运行效率。对于一个需要频繁申请和释放内存的程序来说,频繁调用new/malloc申请内存,delete/free释放内存都需要花费系统时间,频繁的调用必然会降低程序的运行效率。
- 内存碎片:经常申请小块内存,会将物理内存“切”得很碎,导致内存碎片。申请内存的顺序并不是释放内存的顺序,因此频繁申请小块内存必然会导致内存碎片,造成“有内存但是申请不到大块内存”的现象。
2.定长内存池的优点和缺点
针对直接使用new/delete、malloc/free存在的问题,定长内存池的设计思路是:预先开辟一块大内存,程序需要内存时直接从该大块内存中“拿”一块,提高申请和释放内存的效率,同时直接分配大块内存还减少了内存碎片问题。
- 优点:简单粗暴,分配和释放的效率高,解决实际中特定场景下的问题有效。
- 缺点:功能单一,只能解决定长的内存需求,另外占着内存没有释放。
对于STL中的空间配置器就是采用的这种方式,当申请小于128字节的内存就是用定长内存池,当超过时,就直接使用malloc和free
三、整体设计
1.框架
高并发内存池整体框架由以下三部分组成,各部分的功能如下:
- 线程缓存(ThreadCache):每个线程独有线程缓存,主要解决多线程下高并发运行场景线程之间的锁竞争问题。线程缓存模块可以为线程提供小于64k内存的分配,并且多个线程并发运行不需要加锁。线程从这里申请内存不需要加锁,每个线程独享一个ThreadCache,这也就是这个并发内存池高效的地方(这就是tcmalloc名字的本质来源,在这里具体的实现采用的是TLS(thread local storage 本地线程存储,可以理解为每个线程独有的全局变量,但是是本地的)))。(本质上ThreadCache里面就是由hash映射的定长的内存桶)
- 中心缓存(CentralCache):中心缓存是所有线程所共享,thread cache是按需从CentralCache中获取的对象。CentralCache周期性的回收thread cache中的对象,避免一个线程占用了太多的内存,而其他线程的内存吃紧。达到内存分配在多个线程中更均衡的按需调度的目的。CentralCache是存在竞争的,所以从这里取内存对象是需要加锁,(比如说一个线程Thread Cache下的8字节映射的自由链表过长,就需要还回CentralCache,但是此时另外的一个线程Thread Cache下8字节的自由链表没有了,就需要向CentralCache要,此时就需要加锁,因为这种情况很少会出现)不过一般情况下在这里取内存对象的效率非常高,所以这里竞争不会很激烈。
- 页缓存(PageCache):以页为单位申请内存,为中心控制缓存提供大块内存。当中心控制缓存中没有内存对象时,可以从PageCache中以页为单位按需获取大块内存,同时PageCache还