发布时间:2026/7/23 21:52:35
thread cache模拟设计实现 1 介绍thread cacheThreadCache是每一个线程自己独占的内存缓冲区。一个线程对应一个 Thread‑Cache别的线程访问不到只负责分配小于 256KB 的小对象最大优势线程申请和释放内存全程不用加锁性能极高concurrent memory pool 主要由下图的3个部分构成其中thread cache就是每个线程独立拥有Central Cache 全局公共仓库所有线程共享PageCache 页缓存全局唯一为了方便后续链接三部分我这里简单介绍一下各部分后续具体写完三个部分我会出一期详细的文章将三者串起来。2.实现thread cache2.1相关框架的实现首先可以建立俩个文件threadCache.h用于声明再建立threadCache.cpp用于定义,然后这里为了代码的完整性我将整体项目普遍需要的代码放到comm.h的头文件下边。这里理解一下thread cache的内部结构就是分为若干个桶每个桶是一个桶位置映射大小的内存块对象的自由链表。下边的代码只是方便理解记忆不一定可以跑起来。//common.h //就是包含这个项目普遍需要的内容 #includeiostream #includeassert using std::cout; using std::endl; #includevector #includetime.h //为了增减代码的可读性 设立这个函数来封装*(void**)//这个是啥意思上个文章里边讲过 static void* nextobj(void* obj)//加的原因是返回值可以修改不只是可读 { return *(void**)obj; } //创建一个链表 class FreeList { public: //链表上边增加和删除 void push(void* obj)//就是相等于释放内存 { assert(obj); //头插 nextobj(obj) _freeList; _freeList obj; } void* pop()//就是相等于申请内存就是把需要的内存从threadCache中删掉 { //头删除 void* obj _freeList; _freeList nextobj(obj); return obj; } private: void* _freeList nullptr; };#threadCache.h #includecommon.h//就是大部分需要的文件头都放里边 class ThreadCache { public: //申请和释放内存 void* Allocate(size_t size);//申请内存其实就是从threadcache中删东西出去 void Deallocate(void* ptr, size_t size);//释放内存就是加东西到threadcache中 private: FreeList freeList[];//这里是threadcache内存的结构 开辟的大小是桶的个数 这个后边再详细讲 };//threadCache.cpp #include common.h #include threadCache.h //申请内存 void* ThreadCache::Allocate(size_t size)//size就是用户申请的内存字节数 { assert(size 256 * 1024);//因为只有申请内存小于等于256kb才可以走threadCache //... } void ThreadCache::Deallocate(void* ptr, size_t size) { assert(ptr); assert(size 256 * 1024); //... }注意1.我为了提高代码的可读性封装了*(void**)记得前边加static因为是全局函数有风险所以加 static 限制仅当前头文件可见2.threadCache.h文件中的FreeList freeList[]需要定义大小不然跑不通后续讲具体需要多少个桶的时候再完善2.2threadCache类函数的完善前文已经介绍 ThreadCache 内部结构接下来我们需要确定 ThreadCache 划分的桶数量并明确每个桶对应的内存规格与存取逻辑。下边我将讲俩个细节问题来感受thread cache内部的内存是如何分配的问题一如何确定自由链表节点的标准内存大小我们简单设想全部统一 8 字节对齐会暴露出一个严重问题最大 256KB如果全程 8 字节一档数组需要 32768 个桶占用大量内存。TCMalloc 采用分段梯度对齐方案在保证内存内碎片浪费不超过 10%的前提下小块内存细粒度对齐大块内存放大对齐步长大幅减少桶的总数量。就按照上图的方式分梯度对齐拿具体数字举个例子申请1030字节区间(1024,8KB]步长 128 向上对齐找到≥1030 最小的 128 倍数 1024 是 128×8下一档 128×9 1152 字节浪费 1152 - 1030 122 浪费比例122 / 1152 ≈ 10.59%大概就是10%左右所以减少了内碎片的浪费。问题二如何确定申请内存对应的桶下标如何利用对齐后的块大小换算得到自由链表数组对应的桶下标从而定位到对应的空闲内存桶。大概的思路就是我们提前写一个数组内容是每一个区间对应的有几个桶分别是16565656最后一个区间桶式总数用不上。然后区分 size 所属区间传入对应对齐移位值调用_Index子函数通过位运算快速完成下标计算叠加前面所有区间桶的总数量也就是提前写好的数组得到全局唯一数组下标。我们这里直接举例子申请1030字节对齐结果 1152 字节这样我们确定区间是(1024,8*1024],1030-1024/ 128 0所以对应的下标就是16 0 16。俩个问题讲清楚了接下来我们就是代码实现我们需要对这俩个进行封装为sizeClass放在common.h里。还有个点就是我们现在确定thread cache中桶的个数了所以单独定义为全局静态变量。//common.h //就是包含这个项目普遍需要的内容 #includeiostream #includeassert using std::cout; using std::endl; #includevector #includetime.h static const size_t MAX_BYTES 256 * 1024;//定义一个最大值 因为如果内存申请小于256kb就是先获取thread cache对象 static const size_t NFREELIST 208;//这里是threadCache中桶的个数 通过按照计算得知 //为了增减代码的可读性 设立这个函数来封装*(void**)//这个是啥意思上个文章里边讲过 static void* nextobj(void* obj)//加的原因是返回值可以修改不只是可读 { return *(void**)obj; } //创建一个链表 class FreeList { public: //链表上边增加和删除 void push(void* obj)//就是相等于释放内存 { assert(obj); //头插 nextobj(obj) _freeList; _freeList obj; } void* pop()//就是相等于申请内存就是把需要的内存从threadCache中删掉 { //头删除 void* obj _freeList; _freeList nextobj(obj); return obj; } private: void* _freeList nullptr; }; class sizeClass { public: //首先实现问题一 //static inline size_t _RoundUp(size_t size, size_t alignNum)//size代表着申请内存的大小 alignNum代表着对其基数 //{ // size_t alignSize; // if (size % alignNum ! 0) // alignSize (size / alignNum 1) * alignNum; // else // alignSize size;//就是刚好等于分界点 // return alignSize; //} //利用二进制方式实现 static inline size_t _RoundUp(size_t size, size_t alignNum) { return ((size alignNum - 1) ~(alignNum - 1));//这里利用二进制的方式很巧妙 } static inline size_t RoundUp(size_t size) { if (size 128) { return _RoundUp(size, 8); } else if (size 1024) { return _RoundUp(size, 16); } else if (size 8 * 1024) { return _RoundUp(size, 128); } else if (size 64 * 1024) { return _RoundUp(size, 1024); } else if (size 256 * 1024) { return _RoundUp(size, 8 * 1024); } else { assert(false); return -1; } } //实现问题二 //计算映射的哪一个自由链表桶从零开始的一共208个 /*size_t _Index(size_t size, size_t alignNum) { if (size % alignNum 0) { return size / alignNum - 1; } else { return size / alignNum; } }*/ //利用二进制实现 static inline size_t _Index(size_t size, size_t align_shift)//aline_shift 对应的就是2的n次方 { return ((size (1 align_shift) - 1) align_shift) - 1;//这里的位移相当于乘除 } static inline size_t Index(size_t size) { // 每个区间有多少个链 static int group_array[4] { 16, 56, 56, 56 };//每一种分段对应的桶的个数 if (size 128) { return _Index(size, 3); } else if (size 1024) { return _Index(size - 128, 4) group_array[0]; } else if (size 8 * 1024) { return _Index(size - 1024, 7) group_array[1] group_array[0]; } else if (size 64 * 1024) { return _Index(size - 8 * 1024, 10) group_array[2] group_array[1] group_array[0]; } else if (size 256 * 1024) { return _Index(size - 64 * 1024, 13) group_array[3] group_array[2] group_array[1] group_array[0]; } else { assert(false); } return -1; } };当我们封装好上边这俩个函数的时候就可以来实现申请和释放内存了这里没什么理解的看代码就能明白直接上代码。//threadCache.cpp #include common.h #include threadCache.h void* threadCache::FetchFromCentralCache(size_t index, size_t size)//这里不是重点 只是为了让代码跑起来 { // ... return nullptr; } //申请内存 void* ThreadCache::Allocate(size_t size)//size就是用户申请的内存字节数 { assert(size MAX_BYTES);//因为只有申请内存小于等于256kb才可以走threadCache size_t alignSize sizeClass::GroudUp(size); size_t index sizeClass::Index(size); if(!_freeList[index].Empty()) // 如果这个桶的链表中还有空节点就可以直接申请 return _freeList[index].pop; else // 向central cache 里边申请 FetchFromCentralCache(index, size); } //释放内存 void ThreadCache::Deallocate(void* ptr, size_t size) { assert(ptr); assert(size MAX_BYTES); size_t index sizeClass::Index(size); _freeList[index].push(ptr); }上边实现申请和释放内存的时候新增了一个判断链表是否为空的函数还有到central cache里边申请内存的函数接下来完善这俩个的声明和定义#threadCache.h #includecommon.h//就是大部分需要的文件头都放里边 class ThreadCache { public: //申请和释放内存 void* Allocate(size_t size);//申请内存其实就是从threadcache中删东西出去 void Deallocate(void* ptr, size_t size);//释放内存就是加东西到threadcache中 // 从中心缓存获取对象 void* FetchFromCentralCache(size_t index, size_t size); private: FreeList freeList[];//这里是threadcache内存的结构 开辟的大小是桶的个数 这个后边再详细讲 };//common.h //就是包含这个项目普遍需要的内容 #includeiostream #includeassert using std::cout; using std::endl; #includevector #includetime.h static const size_t MAX_BYTES 256 * 1024;//定义一个最大值 因为如果内存申请小于256kb就是先获取thread cache对象 static const size_t NFREELIST 208;//这里是threadCache中桶的个数 通过按照计算得知 //为了增减代码的可读性 设立这个函数来封装*(void**)//这个是啥意思上个文章里边讲过 static void* nextobj(void* obj)//加的原因是返回值可以修改不只是可读 { return *(void**)obj; } //创建一个链表 class FreeList { public: //链表上边增加和删除 void push(void* obj)//就是相等于释放内存 { assert(obj); //头插 nextobj(obj) _freeList; _freeList obj; } void* pop()//就是相等于申请内存就是把需要的内存从threadCache中删掉 { //头删除 void* obj _freeList; _freeList nextobj(obj); return obj; } bool Empty() { return _freeList nullpty; } private: void* _freeList nullptr; }; class sizeClass { public: //首先实现问题一 //static inline size_t _RoundUp(size_t size, size_t alignNum)//size代表着申请内存的大小 alignNum代表着对其基数 //{ // size_t alignSize; // if (size % alignNum ! 0) // alignSize (size / alignNum 1) * alignNum; // else // alignSize size;//就是刚好等于分界点 // return alignSize; //} //利用二进制方式实现 static inline size_t _RoundUp(size_t size, size_t alignNum) { return ((size alignNum - 1) ~(alignNum - 1));//这里利用二进制的方式很巧妙 } static inline size_t RoundUp(size_t size) { if (size 128) { return _RoundUp(size, 8); } else if (size 1024) { return _RoundUp(size, 16); } else if (size 8 * 1024) { return _RoundUp(size, 128); } else if (size 64 * 1024) { return _RoundUp(size, 1024); } else if (size 256 * 1024) { return _RoundUp(size, 8 * 1024); } else { assert(false); return -1; } } //实现问题二 //计算映射的哪一个自由链表桶从零开始的一共208个 /*size_t _Index(size_t size, size_t alignNum) { if (size % alignNum 0) { return size / alignNum - 1; } else { return size / alignNum; } }*/ //利用二进制实现 static inline size_t _Index(size_t size, size_t align_shift)//aline_shift 对应的就是2的n次方 { return ((size (1 align_shift) - 1) align_shift) - 1;//这里的位移相当于乘除 } static inline size_t Index(size_t size) { // 每个区间有多少个链 static int group_array[4] { 16, 56, 56, 56 };//每一种分段对应的桶的个数 if (size 128) { return _Index(size, 3); } else if (size 1024) { return _Index(size - 128, 4) group_array[0]; } else if (size 8 * 1024) { return _Index(size - 1024, 7) group_array[1] group_array[0]; } else if (size 64 * 1024) { return _Index(size - 8 * 1024, 10) group_array[2] group_array[1] group_array[0]; } else if (size 256 * 1024) { return _Index(size - 64 * 1024, 13) group_array[3] group_array[2] group_array[1] group_array[0]; } else { assert(false); } return -1; } };2.3实现TLS无锁访问使用__declspec(thread)TLS 线程本地存储让每个线程拥有独立的 ThreadCache 指针线程只操作自己专属的 ThreadCache 对象 天然不存在多线程竞争ThreadCache 分配 / 释放全程不需要加锁这也是 TCMalloc 高性能的核心关键点之一。2.3.1定义TLS变量//threadCache.h // TLS Thread Local Storage 线程本地存储 static __declspec(thread) ThreadCache* pTLSThreadCache nullptr;__declspec(thread)修饰的变量特点 进程内每一个线程都会独立拥有一份该变量副本。 线程 A 修改pTLSThreadCache不会影响线程 B 的pTLSThreadCache线程之间完全隔离。所以为了实现TLS并且还是申请内存我们继续做一个上层并发分配接口封装创建一个文件concurrentAlloc.h在这里进行封装//ConcurrentAlloc.h #includecommon.h #includethreadCache.h void* concurrentAlloc(size_T size) { if(pTLSThreadCache nullptr) pTLSThreadCache new ThreadCache; return pTLSThreadCache-Allocate(size); } void concurrentFree(void* ptr, size_t size) { assert(ptr); pTLSThreadCache-Deallocate(ptr, size); }到这我们就基本写好了下边我来写一个测试来测试一下我们上边写的代码//unitTest.cpp void Alloc1()//线程1 { for (size_t i 0; i 5; i) { void* ptr concurrentAlloc(6); } } void Alloc2()//线程2 { for (size_t i 0; i 5; i) { void* ptr concurrentAlloc(7); } } void TLSTest() { //串行执行两个线程 std::thread t1(Alloc1); std::thread t2(Alloc2); t1.join(); t2.join(); }线程 1 第一次执行concurrentAllocpTLSThreadCache nullptrnew 一个专属ThreadCache后续复用。线程 2 拥有独立副本的pTLSThreadCache不受线程 1 影响同样新建属于自己的ThreadCache。并行的执行俩个线程这里需要包含头文件thread最后在main函数中调用执行就欧克。3.全部代码展示3.1common.h//common.h //就是包含这个项目普遍需要的内容 #includeiostream #includeassert using std::cout; using std::endl; #includevector #includetime.h #includethread static const size_t MAX_BYTES 256 * 1024;//定义一个最大值 因为如果内存申请小于256kb就是先获取thread cache对象 static const size_t NFREELIST 208;//这里是threadCache中桶的个数 通过按照计算得知 //为了增减代码的可读性 设立这个函数来封装*(void**)//这个是啥意思上个文章里边讲过 static void* nextobj(void* obj)//加的原因是返回值可以修改不只是可读 { return *(void**)obj; } //创建一个链表 class FreeList { public: //链表上边增加和删除 void push(void* obj)//就是相等于释放内存 { assert(obj); //头插 nextobj(obj) _freeList; _freeList obj; } void* pop()//就是相等于申请内存就是把需要的内存从threadCache中删掉 { //头删除 void* obj _freeList; _freeList nextobj(obj); return obj; } bool Empty() { return _freeList nullpty; } private: void* _freeList nullptr; }; class sizeClass { public: //首先实现问题一 //static inline size_t _RoundUp(size_t size, size_t alignNum)//size代表着申请内存的大小 alignNum代表着对其基数 //{ // size_t alignSize; // if (size % alignNum ! 0) // alignSize (size / alignNum 1) * alignNum; // else // alignSize size;//就是刚好等于分界点 // return alignSize; //} //利用二进制方式实现 static inline size_t _RoundUp(size_t size, size_t alignNum) { return ((size alignNum - 1) ~(alignNum - 1));//这里利用二进制的方式很巧妙 } static inline size_t RoundUp(size_t size) { if (size 128) { return _RoundUp(size, 8); } else if (size 1024) { return _RoundUp(size, 16); } else if (size 8 * 1024) { return _RoundUp(size, 128); } else if (size 64 * 1024) { return _RoundUp(size, 1024); } else if (size 256 * 1024) { return _RoundUp(size, 8 * 1024); } else { assert(false); return -1; } } //实现问题二 //计算映射的哪一个自由链表桶从零开始的一共208个 /*size_t _Index(size_t size, size_t alignNum) { if (size % alignNum 0) { return size / alignNum - 1; } else { return size / alignNum; } }*/ //利用二进制实现 static inline size_t _Index(size_t size, size_t align_shift)//aline_shift 对应的就是2的n次方 { return ((size (1 align_shift) - 1) align_shift) - 1;//这里的位移相当于乘除 } static inline size_t Index(size_t size) { // 每个区间有多少个链 static int group_array[4] { 16, 56, 56, 56 };//每一种分段对应的桶的个数 if (size 128) { return _Index(size, 3); } else if (size 1024) { return _Index(size - 128, 4) group_array[0]; } else if (size 8 * 1024) { return _Index(size - 1024, 7) group_array[1] group_array[0]; } else if (size 64 * 1024) { return _Index(size - 8 * 1024, 10) group_array[2] group_array[1] group_array[0]; } else if (size 256 * 1024) { return _Index(size - 64 * 1024, 13) group_array[3] group_array[2] group_array[1] group_array[0]; } else { assert(false); } return -1; } };3.2threadCache.h#threadCache.h #includecommon.h//就是大部分需要的文件头都放里边 class ThreadCache { public: //申请和释放内存 void* Allocate(size_t size);//申请内存其实就是从threadcache中删东西出去 void Deallocate(void* ptr, size_t size);//释放内存就是加东西到threadcache中 // 从中心缓存获取对象 void* FetchFromCentralCache(size_t index, size_t size); private: FreeList freeList[NFREELIST];//这里是threadcache内存的结构 开辟的大小是桶的个数 这个后边再详细讲 };3.3threadCache.cpp//threadCache.cpp #include common.h #include threadCache.h void* threadCache::FetchFromCentralCache(size_t index, size_t size)//这里不是重点 只是为了让代码跑起来 { // ... return nullptr; } //申请内存 void* ThreadCache::Allocate(size_t size)//size就是用户申请的内存字节数 { assert(size MAX_BYTES);//因为只有申请内存小于等于256kb才可以走threadCache size_t alignSize sizeClass::GroudUp(size); size_t index sizeClass::Index(size); if(!_freeList[index].Empty()) // 如果这个桶的链表中还有空节点就可以直接申请 return _freeList[index].pop; else // 向central cache 里边申请 FetchFromCentralCache(index, size); } //释放内存 void ThreadCache::Deallocate(void* ptr, size_t size) { assert(ptr); assert(size MAX_BYTES); size_t index sizeClass::Index(size); _freeList[index].push(ptr); }3.4ConcurrentAlloc.h//ConcurrentAlloc.h #includecommon.h #includethreadCache.h // TLS thread local storage 线程本地存储注释 static __declspec(thread) ThreadCache* pTLSThreadCache nullptr; void* concurrentAlloc(size_T size) { if(pTLSThreadCache nullptr) pTLSThreadCache new ThreadCache; return pTLSThreadCache-Allocate(size); } void concurrentFree(void* ptr, size_t size) { assert(ptr); pTLSThreadCache-Deallocate(ptr, size); }3.5unitTest.cpp#includeObjectpool.h #includecommon.h //测试threadCache需要的函数 #includeConcurrentAlloc.h void Alloc1()//线程1 { for (size_t i 0; i 5; i) { void* ptr concurrentAlloc(6); } } void Alloc2()//线程2 { for (size_t i 0; i 5; i) { void* ptr concurrentAlloc(7); } } void TLSTest() { //并行执行 std::thread t1(Alloc1); std::thread t2(Alloc2); t1.join(); t2.join(); } int main() { TLSTest(); }

相关新闻

2026/7/23 21:52:35

2026专业的全球EMBA中立择校测评

民营企业家、创始人选EMBA,核心纠结点集中在国际认可度、课程适配性、圈层质量与产业资源匹配度。本文从全球办学排名、院校办学定位、课程体系、学员圈层、产业资源五大客观维度,对专业的全球EMBA主流项目进行横向测评。全文无商业推广、无夸大宣传&…

2026/7/23 21:52:35

AI Coding变革职场:零基础也能掌握Agent技术,抢占高薪收藏岗!

随着AI技术的飞速发展,传统的前端、后端等技术栈正在被AI Agent工程师所取代。AI能快速生成代码,使得工程师的核心价值从“写代码”转变为设计和监督AI工作。Agent工程师薪资高、发展空间大,是当前招聘市场上的热门岗位。 AI Coding&#xff…

2026/7/23 23:08:05

AI大模型全栈学习指南:从零基础到高薪就业

1. 项目概述:大模型时代的学习突围指南这个资源包本质上是一套针对AI大模型领域的全栈学习解决方案。我花了三个月时间系统梳理了市面上主流的学习路径,结合自己从传统开发转型AI工程师的实战经验,最终形成了这套包含学习路线、面试真题和避坑…

2026/7/23 23:08:05

小米CVPR论文解析:大模型与强化学习驱动自动驾驶创新

1. 小米技术突破背后的CVPR顶会论文解析当我在电脑前刷到小米多篇论文入选CVPR 2026的消息时,第一反应是打开论文列表逐篇研究。作为计算机视觉领域的"奥斯卡",CVPR的入选率常年维持在25%左右,而小米这次在自动驾驶、大模型等前沿方…

2026/7/23 23:08:05

面向AI的金融数据中间件stock-sdk-mcp设计与实践

1. 项目背景与核心价值去年在开发量化策略时,我发现传统金融数据接口存在几个致命痛点:数据清洗成本高、实时性差、API设计不符合AI训练习惯。每次把股票数据喂给模型前,都要写一堆格式转换和异常处理的胶水代码。stock-sdk-mcp这个项目正是为…

2026/7/23 23:08:05

Agentic Data — 面向数据分析的 Agentic

基于 Next.js 15 多模型 LLM 的 Agentic 数据分析平台。用户以自然语言对话的方式上传、探查、清洗、分析数据,平台借助一套类 Claude Code 设计的 Agent 引擎,自动规划、调用工具、自纠错并可视化结果。还能把对话沉淀为可外部调用的 API、把领域知识沉…

2026/7/23 23:08:05

光刻胶性能表征方法与技术(下)

第三节:光刻胶耐刻蚀性评估及等离子体蚀刻选择比计算一、耐刻蚀性评估(一)耐刻蚀性评估的核心指标评估光刻胶的耐刻蚀性,本质上是衡量其在特定刻蚀环境下的图形保持能力。(二)主流的测试与表征方法耐刻蚀性…

2026/7/23 23:03:05

数据结构(串)

串的定义 串,即字符串(String)是由零个或多个字符组成的有限序列。一般记为S′a1a2⋯⋯an′(n≥0)S a_1a_2\cdots\cdots a_n \quad (n \ge 0)S′a1​a2​⋯⋯an′​(n≥0)其中,SSS是串名,单引号括起来的字符序列是串的…

2026/7/23 12:54:51

Unity与Python本地通信:基于Flask的跨语言数据交换实战

1. 项目概述:为什么我们需要一个本地通信服务器?在游戏开发、数字孪生、仿真训练等众多领域,Unity作为强大的实时3D内容创作平台,其核心逻辑通常由C#驱动。然而,当我们需要进行复杂的数据分析、机器学习推理、科学计算…

2026/7/23 0:01:10

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/22 21:00:12

3个高效策略:快速掌握Axure中文界面配置

3个高效策略:快速掌握Axure中文界面配置 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界面感…