參考計數智慧指標(Reference-Counted Smart Pointer)廣泛用於 C++ 專案自動管理記憶體。但災難性關機後的系統恢復場景中,傳統序列化/反序列化會導致大量物件重複建立,記憶體消耗飆升。本文提出標記-快取模式,透過唯一標籤與全域物件快取,實現高效反序列化回收。
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() { /* 持久化計數器 */ }
};
tagimpersonate(tag):
tag 物件
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;
}
};
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,000 | 25 MB | 32 MB | 28% |
| 100,000 | 370 MB | 413 MB | 12% |
| 200,000 | 730 MB | 795 MB | 9% |
記憶體增長率隨規模擴大而下降,證明共享機制有效。任務完成後記憶體最終被回收。
| 機制 | 作用 |
|---|---|
| CReferable::m_tag | 物件全域唯一身分證,跨序列化邊界保持身分 |
| CReferableCache | 全域單例,tag→物件映射,反序列化去重核心 |
| Ref::impersonate() | 智慧指標「附身」:依 tag 切換內部指向快取物件 |
| 序列化只寫 tag | 避免重複寫入共享物件屬性 |
| 反序列化 impersonate | 建立共享引用而非新建物件 |
GetUniqueTag() 需跨進程/重啟單調遞增(檔案/資料庫儲存最大值)WeakRef) 或定期掃描標記-快取模式將反序列化從「物件樹複製」轉為「物件圖重建」,核心在於唯一標籤識別身分與全域快取去重。對於長期運行、大規模共享物件圖的 C++ 系統(排程器、快取伺服器、遊戲伺服器),此模式能大幅降低恢復記憶體峰值,確保系統可伸縮恢復能力。實作關鍵在於 impersonate() 的原子性切換與快取的執行緒安全設計。