Stories

Detail Return Return

手把手教你實現C++高性能內存池,相比 malloc 性能提升7倍! - Stories Detail

大家好,我是小康。

寫在前面

你知道嗎?在高併發場景下,頻繁的mallocfree操作就像是程序的"阿喀琉斯之踵",輕則拖慢系統響應,重則直接把服務器拖垮。

最近我從0到1實現了一個高性能內存池,經過嚴格的壓測驗證,在8B到2048B的分配釋放場景下,性能相比傳統的malloc/free平均快了4.5倍!今天就來給大家分享這個實現過程,相信看完後你也能寫出自己的高性能內存池。

數據最有説服力,來看看實測結果:

看到了嗎?相比標準malloc/free,平均性能提升4.62倍,最高達到7.37倍!

手把手教你實現C++高性能內存池,相比 malloc 性能提升7倍!

為什麼需要內存池?

在開始擼代碼之前,我們先來聊聊為什麼要造這個輪子。

傳統內存分配的痛點

你有沒有遇到過這些情況:

  1. 頻繁分配小對象:比如遊戲服務器中每秒創建成千上萬個臨時對象
  2. 內存碎片化:明明還有很多空閒內存,但就是分配不出連續的大塊
  3. 性能瓶頸:高併發場景下malloc成為系統的性能瓶頸
  4. 內存泄漏:忘記free導致的內存泄漏,讓人頭疼不已

這些問題的根源在於:系統級的內存分配器設計得太通用了。它要處理各種大小的內存請求,要考慮各種邊界情況,這就導致了性能上的妥協。

內存池的優勢

內存池就像是給程序開了個"專屬食堂":

  • 速度快:預先分配好內存,拿來就用,不用每次都找系統要
  • 減少碎片:統一管理,按需切分,內存利用率更高
  • 避免泄漏:集中管理,程序結束時統一釋放
  • 可控性強:自己的地盤自己做主,可以根據業務特點優化

設計思路:三層架構設計

經過大量調研和思考,我採用了類似TCMalloc的三層架構:

┌─────────────────────────────────────────────────────────┐
│                   應用程序                                │
└─────────────────────┬───────────────────────────────────┘
                      │ 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)                       │
└─────────────────────────────────────────────────────────┘

為什麼是三層?

這個設計的精妙之處在於分層解耦

  • ThreadCache:每個線程都有自己的緩存,分配時無需加鎖,速度飛快
  • CentralCache:當ThreadCache沒有合適的內存塊時,向CentralCache申請
  • PageHeap:管理大塊內存,當CentralCache也沒有時,向系統申請內存

這樣設計的好處是:大部分情況下分配操作都在ThreadCache完成,無鎖且極快;只有在必要時才會涉及鎖操作。

第一層:ThreadCache(線程本地緩存)

設計理念:每個線程擁有獨立的內存緩存,消除鎖競爭。

class ThreadCache {
private:
    FreeList free_lists_[NFREELISTS];  // 208個不同大小的自由鏈表
    static thread_local ThreadCache* tls_thread_cache_;
    
public:
    void* Allocate(size_t size);
    void Deallocate(void* ptr, size_t size);
};

核心優化點

  • 無鎖設計:線程本地存儲,天然線程安全
  • 多級緩存:208個不同大小的自由鏈表
  • 批量操作:與中心緩存批量交換,減少交互次數

第二層:CentralCache(中心分配器)

設計理念:所有線程共享的中心分配器,負責向ThreadCache提供內存。

class CentralCache {
private:
    SpanList span_lists_[NFREELISTS];        // Span鏈表數組
    std::mutex mutexes_[NFREELISTS];         // 桶鎖數組
    
public:
    size_t FetchRangeObj(void*& start, void*& end, size_t n, size_t size);
    void ReleaseListToSpans(void* start, size_t size);
};

核心優化點

  • 桶鎖設計:每個大小類別獨立鎖,減少鎖競爭
  • Span管理:每個Span管理一組連續頁面
  • 批量分配:一次分配多個對象給ThreadCache

第三層:PageHeap(頁堆管理器)

設計理念:管理大塊內存頁面,是系統內存和應用的橋樑。

class PageHeap {
private:
    SpanList span_lists_[POWER_SLOTS];  // 只管理2的冪次頁數
    PageMap2<PAGE_MAP_BITS> page_map_;   // 頁面到Span映射,採用基數樹來管理
    
public:
    Span* AllocateSpan(size_t n);
    void ReleaseSpanToPageHeap(Span* span);
};

核心優化點

  • 2的冪次優化:只分配1,2,4,8,16,32,64,128,256頁的Span
  • 智能分裂:大Span智能分裂成小Span
  • 零開銷釋放:釋放直接緩存,無需合併操作

核心數據結構設計

1. 自由鏈表(FreeList)

這是內存池的基礎數據結構,將空閒內存塊串成鏈表:

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*& start, void*& end, size_t n);
};

巧妙設計:利用空閒內存塊本身存儲鏈表指針,零額外開銷!

static inline void*& NextObj(void* obj) {
    return *(void**)obj;  // 前8字節存儲下一個塊的地址
}

2. Span結構

Span是管理連續頁面的核心結構:

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_;          // 是否使用中
};

關鍵算法實現

1. 大小類別映射算法

將任意大小映射到固定的大小類別,這是性能的關鍵:

static inline size_t RoundUp(size_t size) {
    if (size <= 128) {
        return Align(size, 8);        // 8字節對齊
    } else if (size <= 1024) {
        return Align(size, 16);       // 16字節對齊
    } else if (size <= 8 * 1024) {
        return Align(size, 128);      // 128字節對齊
    } else if (size <= 64 * 1024) {
        return Align(size, 1024);     // 1KB對齊
    } else if (size <= 256 * 1024) {
        return Align(size, 8 * 1024); // 8KB對齊
    }
}

設計考量:小對象精細對齊,大對象粗粒度對齊,平衡內存利用率和性能。

2. 慢啓動批量分配

動態調整批量大小,平衡內存使用和性能:

static size_t NumMoveSize(size_t size) {
    size_t base_batch;
    if (size <= 32) {
        base_batch = 128;    // 小對象大批量
    } else if (size <= 128) {
        base_batch = 64;
    } else if (size <= 512) {
        base_batch = 32;
    } else {
        base_batch = 16;     // 大對象小批量
    }
    return base_batch * batch_multiplier;
}

3. 頁面映射優化

採用二層基數樹,快速查找對象所屬的Span:

template<int BITS>
class PageMap2 {
private:
    static const int LEAF_BITS = BITS / 2;
    static const int ROOT_BITS = BITS - LEAF_BITS;
    
    struct OptimizedLeaf {
        SubLeaf* sub_leafs[SUB_LEAFS_PER_LEAF];
        // 延遲初始化,減少內存開銷
    };
    
public:
    inline void* get(size_t k) const;
    inline void set(size_t k, void* v);
};

上面展示的是部分核心設計思路的簡化代碼,實際實現中還包含了更多的邊界處理和優化細節。

PS:説實話,能參考TCMalloc架構手搓高性能內存池的人應該不多。我在研究階段看了網上的幾個版本,發現大部分還是基於32位系統設計的,在如今的64位環境下就顯得有些侷限了。可能是早期教學項目的代碼被反覆借鑑,缺少針對現代系統的深度優化。

注意: 我這個版本從頭開始針對64位系統設計,不僅支持完整的虛擬地址空間,還考慮了現代CPU架構的特性, 至少在設計思路上更貼近實際應用場景。

手把手教你實現C++高性能內存池,相比 malloc 性能提升7倍!

性能優化技巧

1. 分支預測優化

// 利用__builtin_expect優化分支預測
if (__builtin_expect(!list.Empty(), 1)) {
    return list.Pop();  // 大概率走這個分支
}

2. 內聯函數優化

// 熱點函數全部內聯
static inline size_t GetPageID(void* addr) {
    return reinterpret_cast<PageID>(addr) >> PAGE_SHIFT;
}

3. 緩存友好的數據結構

// 64字節對齊,匹配CPU緩存行
struct SimpleBatch {
    void* ptrs[32];    
    uint8_t count = 0;   
} __attribute__((aligned(64)));

4. 鎖優化策略

// 桶鎖:每個大小類別獨立鎖
std::mutex mutexes_[NFREELISTS];

// 減少持鎖時間
{
    std::lock_guard<std::mutex> lock(mutexes_[index]);
    // 最少的臨界區代碼
}

5. 基於perf的性能分析優化

在內存池開發過程中,perf是我最重要的性能分析工具。下面分享三個實際優化案例:

案例1:發現熱點函數並優化

問題發現:使用perf分析發現SizeClass::Index()函數佔用了15%的CPU時間

# 性能分析命令
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)

優化方案:針對最常用的小對象做特殊優化

// 優化前:每次都走複雜的Index計算
size_t index = SizeClass::Index(align_size);

// 優化後:小對象直接計算,避免函數調用
size_t index;
if (__builtin_expect(align_size <= 128, 1)) {
    index = (align_size >> 3) - 1;  // 直接位運算
} else {
    index = SizeClass::Index(align_size);  // 複雜情況才調用
}

效果驗證:再次perf分析,該函數CPU佔用降到3.2%,整體性能提升12%

案例2:優化Deallocate的批量處理

問題發現:Deallocate函數中頻繁的Push操作CPU耗時較高

12.45%  test_memory_pool  [.] FreeList::Push(void*)
 7.33%  test_memory_pool  [.] ThreadCache::Deallocate(void*, unsigned long)

優化方案:針對小對象使用批量釋放策略

// 優化前:每次都要操作鏈表
void ThreadCache::Deallocate(void* ptr, size_t size) {
    size_t index = GetIndex(size);
    free_lists_[index].Push(ptr);  // 每次都要修改鏈表頭
}

// 優化後:使用批量緩衝區
SimpleBatch batches_[32];  // 只為熱點大小創建批量

void ThreadCache::Deallocate(void* ptr, size_t size) {
    if (index < 32) {
        SimpleBatch& batch = batches_[index];
        batch.ptrs[batch.count++] = ptr;
        if (batch.count >= 32) {
            FlushSimpleBatch(index, size);  // 批量刷新到鏈表
        }
    }
}

案例3:解決大量缺頁中斷問題

問題發現:程序出現大量缺頁處理,perf顯示__memset_avx2_erms耗時嚴重

33.67%  test_memory_pool  [.] __memset_avx2_erms
11.22%  test_memory_pool  [.] PageMap2::set_range

優化方案:優化PageMap二層基數樹,減少memset調用

// 優化前:每次都要初始化大塊內存
class PageMap2 {
    void* values[HUGE_SIZE];  // 直接分配巨大數組,導致大量memset
};

// 優化後:延遲初始化,按需分配
class PageMap2 {
    struct SubLeaf {
        void* values[1024];  // 只有8KB,memset很快
        bool initialized = false;
    };
    
    void ensure_initialized() {
        if (!initialized) {
            memset(values, 0, sizeof(values));  // 只清零8KB
            initialized = true;
        }
    }
};

效果:memset調用減少95%,在高併發場景下性能提升顯著。由此可見在高併發場景下不能夠大量調用memset。

實際在優化過程中還遇到了很多類似的性能瓶頸,這裏只是舉了幾個例子。perf工具幫助我們精確定位問題,避免了盲目優化,每一次改進都有數據支撐。

實戰測試與性能分析

測試環境

  • 系統:ubuntu20.04
  • 編譯器:GCC -O3 優化
  • 線程數:16
  • 每線程操作:10000次分配釋放

性能提升分析

  1. 小對象優勢明顯:8B-128B對象提升2-5倍
  2. 中等對象表現優異:256B-1KB對象提升5-6倍
  3. 大對象依然領先:2KB以上對象提升7倍以上

為什麼這麼快?

  1. 減少系統調用:批量分配減少90%+的系統調用
  2. 消除鎖競爭:線程本地緩存 + 桶鎖設計
  3. 內存局部性:連續內存分配,緩存友好
  4. 算法優化:O(1)分配釋放,無遍歷查找

使用方法

接口設計簡潔,可以直接替換malloc/free:

// 基礎接口
void* ptr = ConcurrentAlloc(1024);
ConcurrentFree(ptr);

項目亮點總結

  1. 三層架構設計:清晰的架構層次,職責分離
  2. 多種優化技術:從算法到實現的全方位優化
  3. 生產級質量:完整的錯誤處理和邊界檢查
  4. 高可擴展性:支持自定義配置和擴展
  5. 實測性能卓越:平均4.62倍性能提升

一些思考和收穫

這個內存池項目從構思到完成,前後花了我一個月的業餘時間。

最初只是想解決項目中的性能瓶頸,沒想到越深入越發現內存管理的複雜性。從最簡單的鏈表管理,到三層架構設計,再到各種微觀優化,每一步都讓我對系統底層有了新的認識。

印象最深的是那次perf分析,發現15%的時間竟然消耗在一個看似簡單的Index計算上。這讓我意識到,真正的性能優化往往隱藏在最不起眼的地方。

還有那次為了解決PageMap初始化性能問題,我不得不重新設計了整個二級頁表結構。原本簡單粗暴的大數組分配導致了嚴重的缺頁中斷,perf顯示__memset_avx2_erms佔用了23%的CPU時間。雖然推翻了之前的設計,改用延遲初始化的SubLeaf結構,但看到最終memset調用減少95%的數據時,一切都值得了。

這個項目最大的價值不是代碼本身,而是整個優化思維的建立。

現在我看任何性能問題,都會先想:這是架構問題還是實現問題?是算法複雜度的問題還是常數優化的空間?數據訪問模式是否緩存友好?鎖的粒度是否合理?

如果你也想掌握這項核心技能...

説實話,內存池設計是C++性能優化的核心技能之一。很多大廠面試都會問相關問題,而且在高性能系統開發中經常用到。

這次實現過程中踩過的坑、總結的經驗,我覺得對想深入學習C++底層優化的朋友會很有價值。所以我把整個項目的開發過程、設計思路、優化技巧都整理成了一套完整的實戰課程。

課程特色

  • 項目驅動:從0到1完整實現,每天都有可運行的版本
  • 漸進式學習:10天學習計劃,循序漸進不會卡住
  • 實戰導向:重點講解perf分析、性能調優等實戰技能
  • 源碼+文檔:4000+行完整代碼,詳細的設計文檔

課程內容涵蓋:

  • 三層架構的系統性設計思維
  • 無鎖編程和多線程優化技巧
  • perf工具進行性能分析的實戰方法
  • CPU緩存友好的數據結構設計
  • 從問題發現到解決的完整優化流程

最重要的是,這不只是理論講解,而是一個完整的工程實踐。學完後你將擁有一個可以寫在簡歷上、在面試中展示的高質量項目。

如果你對這個內存池項目感興趣,可以看這篇文章: 手把手教你實現C++高性能內存池,相比 malloc 性能提升7倍!

想報名的話,趕緊加我微信:jkfwdkf,備註「內存池」!

或者掃下方二維碼加我

在這個AI時代,能深入底層、具備系統級優化能力的工程師反而更加稀缺。這個項目的技術深度和工程實踐價值,相信能為你的技術成長和職業發展帶來實質幫助。

如果你在性能優化方面有什麼心得,或者對內存池實現有什麼想法,也歡迎在評論區交流。

Add a new Comments

Some HTML is okay.