我们陪你 發表於 2025-9-17 09:37:00

手把手教你实现C++高性能内存池,相比 malloc 性能提升7倍!

<p>大家好,我是小康。</p>
<h2 id="写在前面">写在前面</h2>
<p>你知道吗?在高并发场景下,频繁的<code>malloc</code>和<code>free</code>操作就像是程序的"阿喀琉斯之踵",轻则拖慢系统响应,重则直接把服务器拖垮。</p>
<p>最近我从0到1实现了一个高性能内存池,经过严格的压测验证,在8B到2048B的分配释放场景下,性能相比传统的<code>malloc/free</code>平均快了<strong>4.5倍</strong>!今天就来给大家分享这个实现过程,相信看完后你也能写出自己的高性能内存池。</p>
<p><strong>数据最有说服力,来看看实测结果</strong>:</p>
<p><img src="https://files.mdnice.com/user/48364/7b3448c7-7126-45cc-ba6f-93d5058e3896.jpg"></p>
<p>看到了吗?<strong>相比标准malloc/free,平均性能提升4.62倍,最高达到7.37倍!</strong></p>
<p>手把手教你实现C++高性能内存池,相比 malloc 性能提升7倍!</p>
<h2 id="为什么需要内存池">为什么需要内存池?</h2>
<p>在开始撸代码之前,我们先来聊聊为什么要造这个轮子。</p>
<h3 id="传统内存分配的痛点">传统内存分配的痛点</h3>
<p>你有没有遇到过这些情况:</p>
<ol>
<li><strong>频繁分配小对象</strong>:比如游戏服务器中每秒创建成千上万个临时对象</li>
<li><strong>内存碎片化</strong>:明明还有很多空闲内存,但就是分配不出连续的大块</li>
<li><strong>性能瓶颈</strong>:高并发场景下<code>malloc</code>成为系统的性能瓶颈</li>
<li><strong>内存泄漏</strong>:忘记<code>free</code>导致的内存泄漏,让人头疼不已</li>
</ol>
<p>这些问题的根源在于:<strong>系统级的内存分配器设计得太通用了</strong>。它要处理各种大小的内存请求,要考虑各种边界情况,这就导致了性能上的妥协。</p>
<h3 id="内存池的优势">内存池的优势</h3>
<p>内存池就像是给程序开了个"专属食堂":</p>
<ul>
<li><strong>速度快</strong>:预先分配好内存,拿来就用,不用每次都找系统要</li>
<li><strong>减少碎片</strong>:统一管理,按需切分,内存利用率更高</li>
<li><strong>避免泄漏</strong>:集中管理,程序结束时统一释放</li>
<li><strong>可控性强</strong>:自己的地盘自己做主,可以根据业务特点优化</li>
</ul>
<h2 id="设计思路三层架构设计">设计思路:三层架构设计</h2>
<p>经过大量调研和思考,我采用了类似TCMalloc的三层架构:</p>
<pre><code class="language-plain">┌─────────────────────────────────────────────────────────┐
│                   应用程序                              │
└─────────────────────┬───────────────────────────────────┘
                      │ ConcurrentAlloc() / ConcurrentFree()
┌─────────────────────▼───────────────────────────────────┐
│            ThreadCache (线程缓存)                      │
│┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐                     │
││ 8B│ │16B│ │32B│ │...│每个线程独享         │
││List │ │List │ │List │ │List │                     │
│└─────┘ └─────┘ └─────┘ └─────┘                     │
└─────────────────────┬───────────────────────────────────┘
                      │ 批量获取/归还
┌─────────────────────▼───────────────────────────────────┐
│             CentralCache (中央缓存)                      │
│┌─────────┐ ┌─────────┐ ┌─────────┐                   │
││ 8B Span │ │16B Span │ │32B Span │全局共享,桶锁    │
││ List    │ │ List    │ │ List    │                   │
│└─────────┘ └─────────┘ └─────────┘                   │
└─────────────────────┬───────────────────────────────────┘
                      │ 申请新Span
┌─────────────────────▼───────────────────────────────────┐
│               PageHeap (页堆)                           │
│┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐                  │
││ 1页│ │ 2页│ │ 4页│ │ 8页│管理大块内存       │
││ Span │ │ Span │ │ Span │ │ Span │                  │
│└──────┘ └──────┘ └──────┘ └──────┘                  │
└─────────────────────┬───────────────────────────────────┘
                      │ 系统调用
┌─────────────────────▼───────────────────────────────────┐
│                  操作系统                               │
│            (mmap/VirtualAlloc)                     │
└─────────────────────────────────────────────────────────┘
</code></pre>
<h3 id="为什么是三层">为什么是三层?</h3>
<p>这个设计的精妙之处在于<strong>分层解耦</strong>:</p>
<ul>
<li><strong>ThreadCache</strong>:每个线程都有自己的缓存,分配时无需加锁,速度飞快</li>
<li><strong>CentralCache</strong>:当ThreadCache没有合适的内存块时,向CentralCache申请</li>
<li><strong>PageHeap</strong>:管理大块内存,当CentralCache也没有时,向系统申请内存</li>
</ul>
<p>这样设计的好处是:大部分情况下分配操作都在ThreadCache完成,无锁且极快;只有在必要时才会涉及锁操作。</p>
<h3 id="第一层threadcache线程本地缓存">第一层:ThreadCache(线程本地缓存)</h3>
<p><strong>设计理念</strong>:每个线程拥有独立的内存缓存,消除锁竞争。</p>
<pre><code class="language-cpp">class ThreadCache {
private:
    FreeList free_lists_;// 208个不同大小的自由链表
    static thread_local ThreadCache* tls_thread_cache_;
   
public:
    void* Allocate(size_t size);
    void Deallocate(void* ptr, size_t size);
};
</code></pre>
<p><strong>核心优化点</strong>:</p>
<ul>
<li><strong>无锁设计</strong>:线程本地存储,天然线程安全</li>
<li><strong>多级缓存</strong>:208个不同大小的自由链表</li>
<li><strong>批量操作</strong>:与中心缓存批量交换,减少交互次数</li>
</ul>
<h3 id="第二层centralcache中心分配器">第二层:CentralCache(中心分配器)</h3>
<p><strong>设计理念</strong>:所有线程共享的中心分配器,负责向ThreadCache提供内存。</p>
<pre><code class="language-cpp">class CentralCache {
private:
    SpanList span_lists_;      // Span链表数组
    std::mutex mutexes_;         // 桶锁数组
   
public:
    size_t FetchRangeObj(void*&amp; start, void*&amp; end, size_t n, size_t size);
    void ReleaseListToSpans(void* start, size_t size);
};
</code></pre>
<p><strong>核心优化点</strong>:</p>
<ul>
<li><strong>桶锁设计</strong>:每个大小类别独立锁,减少锁竞争</li>
<li><strong>Span管理</strong>:每个Span管理一组连续页面</li>
<li><strong>批量分配</strong>:一次分配多个对象给ThreadCache</li>
</ul>
<h3 id="第三层pageheap页堆管理器">第三层:PageHeap(页堆管理器)</h3>
<p><strong>设计理念</strong>:管理大块内存页面,是系统内存和应用的桥梁。</p>
<pre><code class="language-cpp">class PageHeap {
private:
    SpanList span_lists_;// 只管理2的幂次页数
    PageMap2&lt;PAGE_MAP_BITS&gt; page_map_;   // 页面到Span映射,采用基数树来管理
   
public:
    Span* AllocateSpan(size_t n);
    void ReleaseSpanToPageHeap(Span* span);
};
</code></pre>
<p><strong>核心优化点</strong>:</p>
<ul>
<li><strong>2的幂次优化</strong>:只分配1,2,4,8,16,32,64,128,256页的Span</li>
<li><strong>智能分裂</strong>:大Span智能分裂成小Span</li>
<li><strong>零开销释放</strong>:释放直接缓存,无需合并操作</li>
</ul>
<h2 id="核心数据结构设计">核心数据结构设计</h2>
<h3 id="1-自由链表freelist">1. 自由链表(FreeList)</h3>
<p>这是内存池的基础数据结构,将空闲内存块串成链表:</p>
<pre><code class="language-cpp">class FreeList {
private:
    void* head_;      // 链表头指针
    size_t size_;   // 当前大小
    size_t max_size_; // 慢启动最大批量大小
   
public:
    void Push(void* obj);
    void* Pop();
    void PushRange(void* start, void* end, size_t n);
    size_t PopRange(void*&amp; start, void*&amp; end, size_t n);
};
</code></pre>
<p><strong>巧妙设计</strong>:利用空闲内存块本身存储链表指针,零额外开销!</p>
<pre><code class="language-cpp">static inline void*&amp; NextObj(void* obj) {
    return *(void**)obj;// 前8字节存储下一个块的地址
}
</code></pre>
<h3 id="2-span结构">2. Span结构</h3>
<p>Span是管理连续页面的核心结构:</p>
<pre><code class="language-cpp">struct Span {
    PageID page_id_;      // 起始页号
    size_t n_;            // 页数
    Span* next_;            // 双向链表指针
    Span* prev_;
    size_t object_size_;    // 切分的对象大小
    size_t use_count_;      // 已分配对象数
    void* free_list_;       // 切分后的自由链表
    bool is_used_;          // 是否使用中
};
</code></pre>
<h2 id="关键算法实现">关键算法实现</h2>
<h3 id="1-大小类别映射算法">1. 大小类别映射算法</h3>
<p>将任意大小映射到固定的大小类别,这是性能的关键:</p>
<pre><code class="language-cpp">static inline size_t RoundUp(size_t size) {
    if (size &lt;= 128) {
      return Align(size, 8);      // 8字节对齐
    } else if (size &lt;= 1024) {
      return Align(size, 16);       // 16字节对齐
    } else if (size &lt;= 8 * 1024) {
      return Align(size, 128);      // 128字节对齐
    } else if (size &lt;= 64 * 1024) {
      return Align(size, 1024);   // 1KB对齐
    } else if (size &lt;= 256 * 1024) {
      return Align(size, 8 * 1024); // 8KB对齐
    }
}
</code></pre>
<p><strong>设计考量</strong>:小对象精细对齐,大对象粗粒度对齐,平衡内存利用率和性能。</p>
<h3 id="2-慢启动批量分配">2. 慢启动批量分配</h3>
<p>动态调整批量大小,平衡内存使用和性能:</p>
<pre><code class="language-cpp">static size_t NumMoveSize(size_t size) {
    size_t base_batch;
    if (size &lt;= 32) {
      base_batch = 128;    // 小对象大批量
    } else if (size &lt;= 128) {
      base_batch = 64;
    } else if (size &lt;= 512) {
      base_batch = 32;
    } else {
      base_batch = 16;   // 大对象小批量
    }
    return base_batch * batch_multiplier;
}
</code></pre>
<h3 id="3-页面映射优化">3. 页面映射优化</h3>
<p>采用二层基数树,快速查找对象所属的Span:</p>
<pre><code class="language-cpp">template&lt;int BITS&gt;
class PageMap2 {
private:
    static const int LEAF_BITS = BITS / 2;
    static const int ROOT_BITS = BITS - LEAF_BITS;
   
    struct OptimizedLeaf {
      SubLeaf* sub_leafs;
      // 延迟初始化,减少内存开销
    };
   
public:
    inline void* get(size_t k) const;
    inline void set(size_t k, void* v);
};
</code></pre>
<p>上面展示的是部分核心设计思路的简化代码,实际实现中还包含了更多的边界处理和优化细节。</p>
<blockquote>
<p><strong>PS</strong>:说实话,能参考TCMalloc架构手搓高性能内存池的人应该不多。我在研究阶段看了网上的几个版本,发现大部分还是基于32位系统设计的,在如今的64位环境下就显得有些局限了。可能是早期教学项目的代码被反复借鉴,缺少针对现代系统的深度优化。</p>
<p><strong>注意</strong>: 我这个版本从头开始针对64位系统设计,不仅支持完整的虚拟地址空间,还考虑了现代CPU架构的特性, 至少在设计思路上更贴近实际应用场景。</p>
</blockquote>
<p>手把手教你实现C++高性能内存池,相比 malloc 性能提升7倍!</p>
<h2 id="性能优化技巧">性能优化技巧</h2>
<h3 id="1-分支预测优化">1. 分支预测优化</h3>
<pre><code class="language-cpp">// 利用__builtin_expect优化分支预测
if (__builtin_expect(!list.Empty(), 1)) {
    return list.Pop();// 大概率走这个分支
}
</code></pre>
<h3 id="2-内联函数优化">2. 内联函数优化</h3>
<pre><code class="language-cpp">// 热点函数全部内联
static inline size_t GetPageID(void* addr) {
    return reinterpret_cast&lt;PageID&gt;(addr) &gt;&gt; PAGE_SHIFT;
}
</code></pre>
<h3 id="3-缓存友好的数据结构">3. 缓存友好的数据结构</h3>
<pre><code class="language-cpp">// 64字节对齐,匹配CPU缓存行
struct SimpleBatch {
    void* ptrs;   
    uint8_t count = 0;   
} __attribute__((aligned(64)));
</code></pre>
<h3 id="4-锁优化策略">4. 锁优化策略</h3>
<pre><code class="language-cpp">// 桶锁:每个大小类别独立锁
std::mutex mutexes_;

// 减少持锁时间
{
    std::lock_guard&lt;std::mutex&gt; lock(mutexes_);
    // 最少的临界区代码
}
</code></pre>
<h3 id="5-基于perf的性能分析优化">5. 基于perf的性能分析优化</h3>
<p>在内存池开发过程中,perf是我最重要的性能分析工具。下面分享三个实际优化案例:</p>
<p><strong>案例1:发现热点函数并优化</strong></p>
<p>问题发现:使用perf分析发现SizeClass::Index()函数占用了15%的CPU时间</p>
<pre><code class="language-bash"># 性能分析命令
sudo perf record -g ./test_memory_pool
sudo perf report

# 发现热点
15.23%test_memory_pool[.] SizeClass::Index(unsigned long)
8.94%test_memory_pool[.] ThreadCache::Allocate(unsigned long)
</code></pre>
<p>优化方案:针对最常用的小对象做特殊优化</p>
<pre><code class="language-cpp">// 优化前:每次都走复杂的Index计算
size_t index = SizeClass::Index(align_size);

// 优化后:小对象直接计算,避免函数调用
size_t index;
if (__builtin_expect(align_size &lt;= 128, 1)) {
    index = (align_size &gt;&gt; 3) - 1;// 直接位运算
} else {
    index = SizeClass::Index(align_size);// 复杂情况才调用
}
</code></pre>
<p>效果验证:再次perf分析,该函数CPU占用降到3.2%,整体性能提升12%</p>
<p><strong>案例2:优化Deallocate的批量处理</strong></p>
<p>问题发现:Deallocate函数中频繁的Push操作CPU耗时较高</p>
<pre><code class="language-bash">12.45%test_memory_pool[.] FreeList::Push(void*)
7.33%test_memory_pool[.] ThreadCache::Deallocate(void*, unsigned long)
</code></pre>
<p>优化方案:针对小对象使用批量释放策略</p>
<pre><code class="language-cpp">// 优化前:每次都要操作链表
void ThreadCache::Deallocate(void* ptr, size_t size) {
    size_t index = GetIndex(size);
    free_lists_.Push(ptr);// 每次都要修改链表头
}

// 优化后:使用批量缓冲区
SimpleBatch batches_;// 只为热点大小创建批量

void ThreadCache::Deallocate(void* ptr, size_t size) {
    if (index &lt; 32) {
      SimpleBatch&amp; batch = batches_;
      batch.ptrs = ptr;
      if (batch.count &gt;= 32) {
            FlushSimpleBatch(index, size);// 批量刷新到链表
      }
    }
}
</code></pre>
<p><strong>案例3:解决大量缺页中断问题</strong></p>
<p>问题发现:程序出现大量缺页处理,perf显示__memset_avx2_erms耗时严重</p>
<pre><code class="language-bash">33.67%test_memory_pool[.] __memset_avx2_erms
11.22%test_memory_pool[.] PageMap2::set_range
</code></pre>
<p>优化方案:优化PageMap二层基数树,减少memset调用</p>
<pre><code class="language-cpp">// 优化前:每次都要初始化大块内存
class PageMap2 {
    void* values;// 直接分配巨大数组,导致大量memset
};

// 优化后:延迟初始化,按需分配
class PageMap2 {
    struct SubLeaf {
      void* values;// 只有8KB,memset很快
      bool initialized = false;
    };
   
    void ensure_initialized() {
      if (!initialized) {
            memset(values, 0, sizeof(values));// 只清零8KB
            initialized = true;
      }
    }
};
</code></pre>
<p>效果:memset调用减少95%,在高并发场景下性能提升显著。由此可见在高并发场景下不能够大量调用memset。</p>
<p>实际在优化过程中还遇到了很多类似的性能瓶颈,这里只是举了几个例子。perf工具帮助我们精确定位问题,避免了盲目优化,每一次改进都有数据支撑。</p>
<h2 id="实战测试与性能分析">实战测试与性能分析</h2>
<h3 id="测试环境">测试环境</h3>
<ul>
<li>系统:ubuntu20.04</li>
<li>编译器:GCC -O3 优化</li>
<li>线程数:16</li>
<li>每线程操作:10000次分配释放</li>
</ul>
<h3 id="性能提升分析">性能提升分析</h3>
<ol>
<li><strong>小对象优势明显</strong>:8B-128B对象提升2-5倍</li>
<li><strong>中等对象表现优异</strong>:256B-1KB对象提升5-6倍</li>
<li><strong>大对象依然领先</strong>:2KB以上对象提升7倍以上</li>
</ol>
<h3 id="为什么这么快">为什么这么快?</h3>
<ol>
<li><strong>减少系统调用</strong>:批量分配减少90%+的系统调用</li>
<li><strong>消除锁竞争</strong>:线程本地缓存 + 桶锁设计</li>
<li><strong>内存局部性</strong>:连续内存分配,缓存友好</li>
<li><strong>算法优化</strong>:O(1)分配释放,无遍历查找</li>
</ol>
<h2 id="使用方法">使用方法</h2>
<p>接口设计简洁,可以直接替换malloc/free:</p>
<pre><code class="language-cpp">// 基础接口
void* ptr = ConcurrentAlloc(1024);
ConcurrentFree(ptr);
</code></pre>
<h2 id="项目亮点总结">项目亮点总结</h2>
<ol>
<li><strong>三层架构设计</strong>:清晰的架构层次,职责分离</li>
<li><strong>多种优化技术</strong>:从算法到实现的全方位优化</li>
<li><strong>生产级质量</strong>:完整的错误处理和边界检查</li>
<li><strong>高可扩展性</strong>:支持自定义配置和扩展</li>
<li><strong>实测性能卓越</strong>:平均4.62倍性能提升</li>
</ol>
<h2 id="一些思考和收获">一些思考和收获</h2>
<p>这个内存池项目从构思到完成,前后花了我一个月的业余时间。</p>
<p>最初只是想解决项目中的性能瓶颈,没想到越深入越发现内存管理的复杂性。从最简单的链表管理,到三层架构设计,再到各种微观优化,每一步都让我对系统底层有了新的认识。</p>
<p><strong>印象最深的是那次perf分析</strong>,发现15%的时间竟然消耗在一个看似简单的Index计算上。这让我意识到,真正的性能优化往往隐藏在最不起眼的地方。</p>
<p>还有那次为了解决PageMap初始化性能问题,我不得不重新设计了整个二级页表结构。原本简单粗暴的大数组分配导致了严重的缺页中断,perf显示__memset_avx2_erms占用了23%的CPU时间。虽然推翻了之前的设计,改用延迟初始化的SubLeaf结构,但看到最终memset调用减少95%的数据时,一切都值得了。</p>
<p><strong>这个项目最大的价值不是代码本身,而是整个优化思维的建立。</strong></p>
<p>现在我看任何性能问题,都会先想:这是架构问题还是实现问题?是算法复杂度的问题还是常数优化的空间?数据访问模式是否缓存友好?锁的粒度是否合理?</p>
<h2 id="如果你也想掌握这项核心技能">如果你也想掌握这项核心技能...</h2>
<p>说实话,内存池设计是C++性能优化的核心技能之一。很多大厂面试都会问相关问题,而且在高性能系统开发中经常用到。</p>
<p>这次实现过程中踩过的坑、总结的经验,我觉得对想深入学习C++底层优化的朋友会很有价值。所以我把整个项目的开发过程、设计思路、优化技巧都整理成了一套完整的实战课程。</p>
<p><strong>课程特色</strong>:</p>
<ul>
<li><strong>项目驱动</strong>:从0到1完整实现,每天都有可运行的版本</li>
<li><strong>渐进式学习</strong>:10天学习计划,循序渐进不会卡住</li>
<li><strong>实战导向</strong>:重点讲解perf分析、性能调优等实战技能</li>
<li><strong>源码+文档</strong>:4000+行完整代码,详细的设计文档</li>
</ul>
<p>课程内容涵盖:</p>
<ul>
<li>三层架构的系统性设计思维</li>
<li>无锁编程和多线程优化技巧</li>
<li>perf工具进行性能分析的实战方法</li>
<li>CPU缓存友好的数据结构设计</li>
<li>从问题发现到解决的完整优化流程</li>
</ul>
<p>最重要的是,这不只是理论讲解,而是一个完整的工程实践。学完后你将拥有一个可以写在简历上、在面试中展示的高质量项目。</p>
<p>如果你对这个内存池项目感兴趣,可以看这篇文章:手把手教你实现C++高性能内存池,相比 malloc 性能提升7倍!</p>
<p>想报名的话,赶紧加我微信:<strong>jkfwdkf</strong>,备注「<strong>内存池</strong>」!</p>
<p><strong>或者扫下方二维码加我</strong>:</p>
<p><img src="https://files.mdnice.com/user/48364/c0dd2bed-8e46-4e05-b082-e8b021e30a16.png"></p>
<p>在这个AI时代,能深入底层、具备系统级优化能力的工程师反而更加稀缺。这个项目的技术深度和工程实践价值,相信能为你的技术成长和职业发展带来实质帮助。</p>
<p>如果你在性能优化方面有什么心得,或者对内存池实现有什么想法,也欢迎在评论区交流。</p><br><br>
来源:https://www.cnblogs.com/xiaokang-coding/p/19096179
頁: [1]
查看完整版本: 手把手教你实现C++高性能内存池,相比 malloc 性能提升7倍!