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

C++ 參考計數智慧指標高效回收機制:標記-快取模式避免物件複製

     編輯:關於C++

前言

參考計數智慧指標(Reference-Counted Smart Pointer)廣泛用於 C++ 專案自動管理記憶體。但災難性關機後的系統恢復場景中,傳統序列化/反序列化會導致大量物件重複建立,記憶體消耗飆升。本文提出標記-快取模式,透過唯一標籤與全域物件快取,實現高效反序列化回收。

問題場景:事件驅動作業排程系統

  • CJobDef:作業定義(靜態屬性:命令、工作目錄、使用者 ID)
  • CJobInst:作業執行實例(動態屬性:PID、參數、歷史記錄)
  • 關係:多個 CJobInst 共享同一 CJobDef

傳統序列化導致的物件膨脹

關機前: 3 個 Instance 共享 1 個 Definition

CJobDef(Def1) ← CJobInst(InstA)
              ← CJobInst(InstB)
              ← CJobInst(InstC)

傳統反序列化後: 每個 Instance 獨擁一個 Definition 複本

CJobDef(Def1_copy1) ← CJobInst(InstA)
CJobDef(Def1_copy2) ← CJobInst(InstB)
CJobDef(Def1_copy3) ← CJobInst(InstC)

記憶體消耗隨實例數線性增長,大規模系統(20萬+ 任務)將耗盡記憶體。

解決方案:標記-快取模式

核心架構


// 可參考基類:提供參考計數與唯一標籤
class CReferable {
private:
    int m_refCount;
    int m_tag;  // 唯一識別碼
protected:
    CReferable() : m_refCount(0), m_tag(GetUniqueTag()) {}
    void increaseReferenceCount() { ++m_refCount; }
    void decreaseReferenceCount() { if (--m_refCount == 0) delete this; }
public:
    int getTag() const { return m_tag; }
    static int GetUniqueTag();  // 全域單調遞增計數器
};

// 智慧指標模板:代理模式
template <class T>
class Ref {
    T* m_ptr;
public:
    Ref(T* p = 0) : m_ptr(p) { if (m_ptr) m_ptr->increaseReferenceCount(); }
    ~Ref() { if (m_ptr) m_ptr->decreaseReferenceCount(); }
    
    T* operator->() { return m_ptr; }
    T& operator*() { return *m_ptr; }
    
    // 關鍵:模擬指標行為
    void set(T* p) { /* 釋放舊、綁定新、增計數 */ }
    void impersonate(int tag);  // 依標籤轉換為快取中物件
};

// 型別別名
typedef Ref CJobDefPtr;
typedef Ref CJobInstPtr;

全域物件快取


class CReferableCache {
    static map s_objects;  // tag → 物件
public:
    static CReferable* getObject(int tag) { 
        auto it = s_objects.find(tag);
        return (it != s_objects.end()) ? it->second : 0;
    }
    static void addObject(CReferable* obj) { s_objects[obj->getTag()] = obj; }
    static void deleteObject(int tag) { s_objects.erase(tag); }
    static int getUniqueTag() { /* 持久化計數器 */ }
};

反序列化流程

  1. 從存檔讀取 tag
  2. 讀取物件屬性資料
  3. 呼叫 impersonate(tag):
    • 查詢快取是否已有該 tag 物件
    • 有:捨棄新建物件,綁定快取物件,避免複製
    • 無:將新物件加入快取,建立索引

實作程式碼

CJobDef 序列化/反序列化


class CJobDef : public CReferable {
    friend CArchive& operator<<(CArchive& ar, const CJobDefPtr& cand) {
        ar << cand->getTag();  // 只寫入標籤
        CArchive ar_def(cand->getTag(), CArchive::WRITE);
        // 寫入屬性到 ar_def
        return ar;
    }
    friend CArchive& operator>>(CArchive& ar, CJobDefPtr& cand) {
        int tag; ar >> tag;  // 讀取標籤
        CArchive ar_def(tag, CArchive::READ);
        // 從 ar_def 讀取屬性
        cand.impersonate(tag);  // 關鍵:依標籤綁定/註冊
        return ar;
    }
};

CJobInst 序列化/反序列化


class CJobInst : public CReferable {
    CJobDefPtr m_defPtr;
    friend CArchive& operator<<(CArchive& ar, const CJobInstPtr& cand) {
        ar << cand->m_defPtr;  // 遞迴序列化定義指標
        return ar;
    }
    friend CArchive& operator>>(CArchive& ar, CJobInstPtr& cand) {
        CJobDefPtr defPtr = new CJobDef;  // 臨時建立
        ar >> defPtr;                     // 反序列化定義(內含 impersonate)
        cand->m_defPtr = defPtr;          // 共享同一 CJobDef 物件
        return ar;
    }
};

運作流程圖解

[反序列化 CJobInst]
       ↓
[讀取 m_defPtr 的 tag]
       ↓
[反序列化 CJobDef 屬性]
       ↓
[impersonate(tag)]
       ↓
┌────────────────────────┐
│ CReferableCache 查詢  │
├────────────────────────┤
│ 找到 → 綁定現有物件    │  ← 避免複製!
│ 未找到 → 加入快取      │
└────────────────────────┘

效能驗證數據

運行任務數重啟前記憶體重啟後記憶體增長率
5,00025 MB32 MB28%
100,000370 MB413 MB12%
200,000730 MB795 MB9%

記憶體增長率隨規模擴大而下降,證明共享機制有效。任務完成後記憶體最終被回收。

關鍵設計要點總結

機制作用
CReferable::m_tag物件全域唯一身分證,跨序列化邊界保持身分
CReferableCache全域單例,tag→物件映射,反序列化去重核心
Ref::impersonate()智慧指標「附身」:依 tag 切換內部指向快取物件
序列化只寫 tag避免重複寫入共享物件屬性
反序列化 impersonate建立共享引用而非新建物件

擴充考量

  • 執行緒安全:快取操作需加鎖(mutex/關鍵區段)
  • 標籤持久化:GetUniqueTag() 需跨進程/重啟單調遞增(檔案/資料庫儲存最大值)
  • 循環參考:純參考計數無法處理,需弱指標 (WeakRef) 或定期掃描
  • 記憶體上限:快取無限增長,需 LRU 淘汰或定期清理

結語

標記-快取模式將反序列化從「物件樹複製」轉為「物件圖重建」,核心在於唯一標籤識別身分與全域快取去重。對於長期運行、大規模共享物件圖的 C++ 系統(排程器、快取伺服器、遊戲伺服器),此模式能大幅降低恢復記憶體峰值,確保系統可伸縮恢復能力。實作關鍵在於 impersonate() 的原子性切換與快取的執行緒安全設計。

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