首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C/C++ 記憶體池設計實戰:消除碎片、提升分配效能、內建洩漏偵測

     編輯:關於C++

痛點:頻繁小塊分配導致堆碎片與效能劣化

長期運行的 C/C++ 程式若頻繁 malloc/new 小塊記憶體,堆會產生大量碎片,分配速度下降、記憶體利用率惡化。記憶體池透過預分配大塊、切分固定大小區塊徹底解決此問題。

核心優勢

  • 近零碎片:固定大小區塊消除外部碎片。
  • 分配/釋放極快:僅操作自由鏈表指標,無系統呼叫開銷。
  • 額外紅利:指標歸屬檢查、堆轉儲、記憶體洩漏斷言(析構時檢查未釋放區塊)。

架構設計


// 區塊描述符
struct SMemoryChunk {
    unsigned char* Data;           // 指向實際資料區
    size_t DataSize;               // 從此區塊到池尾的剩餘位元組數
    size_t UsedSize;               // 實際使用大小(0 表示空閒)
    bool IsAllocationChunk;        // 是否為本次分配的首區塊
    SMemoryChunk* Next;            // 鏈表下一節點
};

// 介面
class IMemoryBlock {
public:
    virtual ~IMemoryBlock() {}
    virtual void* GetMemory(size_t size) = 0;
    virtual void FreeMemory(void* ptr, size_t size) = 0;
};

// 記憶體池實現
class CMemoryPool : public IMemoryBlock {
public:
    CMemoryPool(size_t initPoolSize = 1000,
                size_t chunkSize = 128,
                size_t minAlloc = 256,
                bool fillPattern = false);
    ~CMemoryPool();

    void* GetMemory(size_t size) override;
    void FreeMemory(void* ptr, size_t size) override;

private:
    // 核心私有方法
    bool AllocateMemory(size_t size);                    // 向 OS 申請大塊並切分
    unsigned int CalculateNeededChunks(size_t size);     // 所需區塊數
    size_t CalculateBestMemoryBlockSize(size_t req);     // 對齊後的實際分配大小
    bool LinkChunksToData(SMemoryChunk* chunks, unsigned int count, unsigned char* mem); // 建立鏈表
    bool RecalcChunkMemorySize(SMemoryChunk* first, unsigned int count); // 更新 DataSize
    SMemoryChunk* SetChunkDefaults(SMemoryChunk* c);     // 初始化區塊描述符
    SMemoryChunk* FindChunkSuitableToHoldMemory(size_t size); // 首次適配搜尋
    SMemoryChunk* SkipChunks(SMemoryChunk* start, unsigned int n); // 跳過已用區塊
    void SetMemoryChunkValues(SMemoryChunk* c, size_t used); // 標記已用
    size_t MaxValue(size_t a, size_t b) const;

    // 狀態
    SMemoryChunk *m_first, *m_last, *m_cursor;
    size_t m_totalSize, m_usedSize, m_freeSize, m_chunkSize;
    unsigned int m_chunkCount, m_objectCount;
    bool m_fillPattern;
    size_t m_minAlloc;
};

關鍵演算法細節

分配流程 GetMemory

  1. 計算對齊後大小 bestSize(chunkSize 的倍數)。
  2. 從游標 m_cursor 開始首次適配搜尋 FindChunkSuitableToHoldMemory:
     • 若區塊 DataSize >= bestSize 且 UsedSize == 0 → 找到。
     • 否則按 UsedSize 換算區塊數跳過(SkipChunks)。
  3. 找不到 → 向 OS 申請新大塊(AllocateMemory),再搜尋。
  4. 標記區塊 UsedSize = bestSize,更新統計,返回 chunk->Data。

釋放流程 FreeMemory

(原始碼中為空殼,實際需實現):

  1. 由指標反推區塊描述符(可維護指標→描述符映射表,或利用區塊固定大小反推)。
  2. 將 UsedSize = 0,更新統計,游標可回退至該區塊加速後續分配。
  3. 可選:相鄰空閒區塊合併(若支援變長分配)。

新記憶體塊建立 AllocateMemory

  1. 計算所需區塊數 n = ceil(req / chunkSize)。
  2. 實際分配大小 realSize = n * chunkSize(自動對齊)。
  3. malloc(realSize) 取得資料區,malloc(n * sizeof(SMemoryChunk)) 取得描述符陣列。
  4. LinkChunksToData 將描述符串成鏈表,每個 Data 指向資料區對應偏移。
  5. 標記首區塊 IsAllocationChunk = true,便於整體釋放追蹤。

使用範例:自定義 operator new


CMemoryPool* g_pool = nullptr;

class HeavyObject {
    char buffer[256];  // 假設對象較大
public:
    void* operator new(size_t sz) { return g_pool->GetMemory(sz); }
    void operator delete(void* p, size_t sz) { g_pool->FreeMemory(p, sz); }
};

int main() {
    g_pool = new CMemoryPool(1024*1024, 256);  // 1MB 池,256B 區塊
    HeavyObject* obj = new HeavyObject();      // 從池分配
    delete obj;                                // 歸還池
    delete g_pool;                             // 析構時檢查洩漏
    return 0;
}

進階擴展方向

  • 多尺寸池:維護多個 chunkSize(如 16/64/256/1024),按請求大小選擇池,減少內部碎片。
  • 執行緒安全:關鍵區保護鏈表操作,或採用 Thread-Local Pool + 全域備援。
  • 統計與診斷:峰值使用率、分配/釋放次數分布、洩漏對象類型統計(結合 RTTI)。
  • 釋放合併:支援變長分配時,釋放後合併相鄰空閒區塊(Buddy System 或邊界標記法)。

何時使用記憶體池?

適用場景不適用場景
長期運行、高頻小塊分配(遊戲實體、網路封包、解析器節點)短生命程式、大塊不頻繁分配
嵌入式/實時系統,需確定性分配延遲記憶體極度受限(池本身有描述符開銷)
需內建洩漏偵測、堆轉儲診斷大量使用標準庫容器(需自定義分配器配合)

記憶體池是系統級程式設計師工具箱中的利器,正確使用能讓長期運行服務的記憶體行為可預測、可觀測、可控制。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved