目录
[1. 自由链表(FreeList):管理空闲内存块](#1. 自由链表(FreeList):管理空闲内存块)
[2. 尺寸对齐(SizeClass):内存块规格统一](#2. 尺寸对齐(SizeClass):内存块规格统一)
[3. 线程缓存(ThreadCache):线程私有存储](#3. 线程缓存(ThreadCache):线程私有存储)
[三、ThreadCache 核心逻辑实现](#三、ThreadCache 核心逻辑实现)
[1. 内存分配(Allocate)](#1. 内存分配(Allocate))
[2. 内存释放(Deallocate)](#2. 内存释放(Deallocate))
[3. 对外接口(ConcurrentAlloc/ConcurrentFree)](#3. 对外接口(ConcurrentAlloc/ConcurrentFree))
[1. 本文收获](#1. 本文收获)
[2. 后续待实现功能](#2. 后续待实现功能)
在高性能服务器、高频内存分配场景中,标准库的 malloc/free 往往因为锁竞争、内存碎片等问题无法满足性能要求。基于 TCMalloc(Thread-Caching Malloc)思想实现的多级内存池,通过线程私有缓存 + 中心缓存 + 页缓存的分层设计,能极大降低内存分配的开销。本文将从核心思路到代码实现,详细记录线程缓存(ThreadCache)的学习与开发过程。
一、内存池核心设计思路
多级内存池的核心是 "分级缓存",核心分层如下:
- ThreadCache(线程缓存):每个线程私有,无锁访问,用于小内存块的快速分配 / 释放;
- CentralCache(中心缓存):所有线程共享,按需向 ThreadCache 补充内存,或回收 ThreadCache 的多余内存;
- 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 的实现,最终完成一个高性能的多级内存池。
