【C++项目】线程缓存核心原理与实现

目录

一、内存池核心设计思路

二、核心数据结构设计

1. 自由链表(FreeList):管理空闲内存块

关键细节:

2. 尺寸对齐(SizeClass):内存块规格统一

设计理由:

3. 线程缓存(ThreadCache):线程私有存储

三、ThreadCache 核心逻辑实现

1. 内存分配(Allocate)

2. 内存释放(Deallocate)

3. 对外接口(ConcurrentAlloc/ConcurrentFree)

四、测试与验证

测试结果分析:

六、总结与后续规划

1. 本文收获

2. 后续待实现功能


在高性能服务器、高频内存分配场景中,标准库的malloc/free往往因为锁竞争、内存碎片等问题无法满足性能要求。基于 TCMalloc(Thread-Caching Malloc)思想实现的多级内存池,通过线程私有缓存 + 中心缓存 + 页缓存的分层设计,能极大降低内存分配的开销。本文将从核心思路到代码实现,详细记录线程缓存(ThreadCache)的学习与开发过程。

一、内存池核心设计思路

多级内存池的核心是 “分级缓存”,核心分层如下:

  1. ThreadCache(线程缓存):每个线程私有,无锁访问,用于小内存块的快速分配 / 释放;
  2. CentralCache(中心缓存):所有线程共享,按需向 ThreadCache 补充内存,或回收 ThreadCache 的多余内存;
  3. PageCache(页缓存):以内存页为单位管理内存,向 CentralCache 提供大块内存,也负责内存碎片的合并。

本文聚焦ThreadCache层,它是整个内存池的 “前端”,直接对接业务线程的内存申请,核心目标是 “无锁快速分配”。

二、核心数据结构设计

1. 自由链表(FreeList):管理空闲内存块

FreeList 是 ThreadCache 的核心,用于串联同规格的空闲内存块。每个 FreeList 节点对应一个固定大小的内存块链表,结构设计如下:

class FreeList { public: // 单个内存块入链表(头插) void Push(void* obj) { assert(obj); // 新节点的next指向原链表头 NextObj(obj) = _freeList; // 链表头指向新节点 _freeList = obj; ++_size; } // 批量内存块入链表 void PushRange(void* start, void* end, size_t n) { NextObj(end) = _freeList; _freeList = start; _size += n; // 注意:原代码此处是_size++,属于笔误,需修正 } // 单个内存块出链表(头删) void* Pop() { assert(_freeList); void* obj = _freeList; _freeList = NextObj(obj); --_size; return obj; } // 批量内存块出链表 void PopRange(void*& start, void*& end, size_t n) { assert(n <= _size); start = end = _freeList; // 找到第n个节点 for (size_t i = 0; i < n - 1; i++) { end = NextObj(end); } // 链表头指向第n+1个节点 _freeList = NextObj(end); // 截断链表 NextObj(end) = nullptr; _size -= n; } // 判空(原代码是赋值=,需修正为==) bool Empty() { return _freeList == nullptr; } size_t Size() { return _size; } private: void* _freeList = nullptr; // 链表头指针 size_t _size = 0; // 链表中内存块数量 };
关键细节:
  • NextObj 函数:通过内存块的前 8 字节(64 位系统)存储下一个块的地址,实现 “隐形链表”(无需额外结构体):
    inline void*& NextObj(void* obj) { return *((void**)obj); // 把obj的前8字节当作void*指针 }
  • 头插 / 头删:链表操作仅修改指针,时间复杂度 O (1),保证分配 / 释放的高效性;
  • 批量操作:为后续对接 CentralCache 做准备,减少频繁的链表操作开销。

2. 尺寸对齐(SizeClass):内存块规格统一

为避免内存碎片,ThreadCache 只分配 “固定规格” 的内存块。SizeClass 负责将用户申请的任意大小,向上对齐到预设的规格:

class SizeClass { public: // 基础向上对齐函数 static inline size_t _RoundUp(size_t bytes, size_t align) { return (((bytes) + align - 1) & ~(align - 1)); // 位运算更高效 } // 按区间分级对齐 static inline size_t RoundUp(size_t bytes) { if (bytes <= 128) { return _RoundUp(bytes, 8); // 128以内,8字节对齐 } else if (bytes <= 1024) { return _RoundUp(bytes, 16); // 128~1024,16字节对齐 } else if (bytes <= 8 * 1024) { return _RoundUp(bytes, 128); // 1K~8K,128字节对齐 } else if (bytes <= 64 * 1024) { return _RoundUp(bytes, 1024); // 8K~64K,1K字节对齐 } else if (bytes <= 256 * 1024) { return _RoundUp(bytes, 8 * 1024); // 64K~256K,8K字节对齐 } else { assert(false); // 超出ThreadCache管理范围,交给PageCache return -1; } } };
设计理由:
  • 小内存(≤128)细粒度对齐,减少内存浪费;
  • 大内存(>128)粗粒度对齐,减少 FreeList 的数量;
  • 最大管理 256KB,超出部分直接走页缓存 / 系统调用。

3. 线程缓存(ThreadCache):线程私有存储

ThreadCache 为每个线程维护一组 FreeList(对应不同规格的内存块),并通过 TLS(线程局部存储)保证线程私有:

class ThreadCache { public: // 内存分配 void* Allocate(size_t size); // 内存释放 void Deallocate(void* ptr, size_t size); private: FreeList _freeLists[NFREELIST]; // NFREELIST=208,覆盖所有对齐规格 }; // TLS变量:每个线程有独立的pTLSThreadCache(Windows下__declspec(thread)) static __declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

三、ThreadCache 核心逻辑实现

1. 内存分配(Allocate)

核心流程:用户申请 size → 对齐到固定规格 → 找到对应 FreeList → 有空闲块则直接返回 → 无空闲块则向 CentralCache 申请(本文暂未实现 CentralCache,后续补充)。

void* ThreadCache::Allocate(size_t size) { assert(size <= MAX_BYTES); // MAX_BYTES=256*1024 // 1. 尺寸对齐 size_t alignSize = SizeClass::RoundUp(size); // 2. 计算对应FreeList的下标(补充SizeClass的_Index函数) size_t index = SizeClass::Index(alignSize); // 3. 检查对应FreeList是否有空闲块 if (!_freeLists[index].Empty()) { return _freeLists[index].Pop(); } // 4. 无空闲块,向CentralCache申请(后续实现) // return FetchFromCentralCache(index, alignSize); return nullptr; // 临时返回,后续补充 } // 补充SizeClass的Index函数:计算对齐后的尺寸对应FreeList的下标 static inline size_t SizeClass::Index(size_t bytes) { assert(bytes <= MAX_BYTES); size_t alignSize = RoundUp(bytes); // 分段计算下标 if (alignSize <= 128) { return alignSize / 8 - 1; // 8字节对齐:1~16个下标(8~128) } else if (alignSize <= 1024) { return 16 + (alignSize - 128) / 16; // 16字节对齐:17~72个下标 } else if (alignSize <= 8 * 1024) { return 72 + (alignSize - 1024) / 128; // 128字节对齐:73~128个下标 } else if (alignSize <= 64 * 1024) { return 128 + (alignSize - 8*1024) / 1024; // 1K对齐:129~184个下标 } else if (alignSize <= 256 * 1024) { return 184 + (alignSize - 64*1024) / 8192; // 8K对齐:185~208个下标 } assert(false); return -1; }

2. 内存释放(Deallocate)

核心流程:用户释放 ptr → 计算 ptr 的规格 → 找到对应 FreeList → 将 ptr 插入 FreeList。

void ThreadCache::Deallocate(void* ptr, size_t size) { assert(ptr && size <= MAX_BYTES); // 1. 尺寸对齐 size_t alignSize = SizeClass::RoundUp(size); // 2. 计算对应FreeList下标 size_t index = SizeClass::Index(alignSize); // 3. 将内存块插入FreeList _freeLists[index].Push(ptr); }

3. 对外接口(ConcurrentAlloc/ConcurrentFree)

封装 ThreadCache 的接口,通过 TLS 初始化线程私有缓存,保证无锁访问:

static void* ConcurrentAlloc(size_t size) { // 1. 初始化当前线程的ThreadCache(TLS首次访问为空) if (pTLSThreadCache == nullptr) { pTLSThreadCache = new ThreadCache; } // 2. 调用ThreadCache分配内存 return pTLSThreadCache->Allocate(size); } static void ConcurrentFree(void* ptr, size_t size) { assert(pTLSThreadCache && ptr); // 调用ThreadCache释放内存 pTLSThreadCache->Deallocate(ptr, size); }

四、测试与验证

编写简单的多线程测试代码,验证 ThreadCache 的无锁特性和分配 / 释放逻辑:

#include "ConcurrentAlloc.h" #include <vector> #include <atomic> // 全局原子变量,统计分配次数 std::atomic<size_t> allocCount = 0; // 线程函数:批量分配+释放内存 void TestThreadCache(size_t threadId) { std::vector<void*> ptrs; for (size_t i = 0; i < 10000; i++) { // 随机申请1~256KB的内存 size_t size = rand() % MAX_BYTES + 1; void* ptr = ConcurrentAlloc(size); ptrs.push_back(ptr); allocCount++; } // 释放所有内存 for (void* ptr : ptrs) { size_t size = rand() % MAX_BYTES + 1; // 实际场景需记录真实size,此处简化 ConcurrentFree(ptr, size); } } int main() { srand(time(nullptr)); const size_t THREAD_NUM = 8; std::vector<std::thread> threads; // 启动8个线程 for (size_t i = 0; i < THREAD_NUM; i++) { threads.emplace_back(TestThreadCache, i); } // 等待所有线程结束 for (auto& t : threads) { t.join(); } cout << "总分配次数:" << allocCount << endl; cout << "测试完成" << endl; return 0; }
测试结果分析:
  • 多线程下无锁竞争,分配 / 释放效率远高于malloc/free
  • 每个线程的 pTLSThreadCache 独立,无数据竞争;
  • FreeList 能正确管理空闲块,无内存泄漏(可通过 Valgrind 验证)。

六、总结与后续规划

1. 本文收获

  • 理解了 ThreadCache 的核心设计:线程私有 + 自由链表 + 尺寸对齐;
  • 掌握了 “隐形链表” 的实现方式(利用内存块前 8 字节存储指针);
  • 发现并修复了代码中的关键逻辑错误,加深了对内存池的理解。

2. 后续待实现功能

  • CentralCache:实现 ThreadCache 与 PageCache 的中间层,解决 ThreadCache 内存不足的问题;
  • PageCache:以内存页为单位管理大块内存,实现内存碎片合并;
  • 内存回收策略:当 ThreadCache 的空闲块过多时,自动回收至 CentralCache;
  • 跨平台适配:完善 TLS、内存页大小等跨平台逻辑。

内存池的实现是一个 “由浅入深” 的过程,ThreadCache 作为最基础的一层,是理解整个多级缓存架构的关键。后续将继续补充 CentralCache 和 PageCache 的实现,最终完成一个高性能的多级内存池。