設計目標
- 固定大小區塊分配器,適用於頻繁 `new/delete` 同尺寸物件 (如節點、訊息封包)。
- 減少系統呼叫、避免碎片、提升分配速度。
- 單例/多實例均可,執行緒不安全 (多執行緒需加鎖或用 Thread-Local Pool)。
核心結構
// 記憶體區塊 (鏈表節點)
struct MemoryBlock {
unsigned short m_nSize; // 總位元組數
unsigned short m_nFree; // 可用單元數
unsigned short m_nFirst; // 首個可用單元索引
MemoryBlock* m_pNext; // 下一區塊
char m_data[1]; // 柔性陣列 (實際大小 = m_nSize)
// placement new 分配整個區塊 (含柔性陣列空間)
void* operator new(size_t, unsigned short sum, unsigned short unit_size) {
return ::operator new(sizeof(MemoryBlock) + sum * unit_size);
}
void operator delete(void* p, size_t) { ::operator delete(p); }
MemoryBlock(unsigned short sum, unsigned short unit_size)
: m_nSize(sum * unit_size), m_nFree(sum - 1), m_nFirst(1), m_pNext(nullptr) {
char* p = m_data;
for (int i = 1; i < sum; ++i) {
*reinterpret_cast(p) = i; // 鏈接下一個空閒單元
p += unit_size;
}
}
};
記憶體池管理類
class MemoryPool {
unsigned short m_nUnitSize; // 對齊後單元大小
unsigned short m_nInitSize; // 首區塊單元數
unsigned short m_nGrowSize; // 後續區塊單元數
MemoryBlock* m_pFirst; // 區塊鏈表頭
public:
MemoryPool(unsigned short unit_size, unsigned short init_size = 2048, unsigned short grow_size = 1024)
: m_pFirst(nullptr) {
// 對齊至 2 位元組 (可改 4/8/16)
if (unit_size > 4) m_nUnitSize = (unit_size + 1) & ~1;
else if (unit_size >= 2) m_nUnitSize = 4;
else m_nUnitSize = 2;
m_nInitSize = init_size;
m_nGrowSize = grow_size;
}
void* Alloc() {
if (!m_pFirst) { // 首次分配
m_pFirst = new (m_nInitSize, m_nUnitSize) MemoryBlock(m_nInitSize, m_nUnitSize);
return m_pFirst->m_data;
}
// 尋找有空閒單元的區塊
MemoryBlock* blk = m_pFirst;
while (blk && blk->m_nFree == 0) blk = blk->m_pNext;
if (blk) {
char* p = blk->m_data + blk->m_nFirst * m_nUnitSize;
blk->m_nFirst = *reinterpret_cast(p);
--blk->m_nFree;
return p;
}
// 無可用區塊 → 增長
if (m_nGrowSize == 0) return nullptr;
blk = new (m_nGrowSize, m_nUnitSize) MemoryBlock(m_nGrowSize, m_nUnitSize);
blk->m_pNext = m_pFirst;
m_pFirst = blk;
return blk->m_data;
}
void Free(void* p) {
if (!m_pFirst) return;
MemoryBlock *blk = m_pFirst, *prev = m_pFirst;
while (blk && (reinterpret_cast(p) < reinterpret_cast(blk->m_data) ||
reinterpret_cast(p) >= reinterpret_cast(blk->m_data) + blk->m_nSize)) {
prev = blk;
blk = blk->m_pNext;
}
if (!blk) return; // 指標不屬於本池
++blk->m_nFree;
*reinterpret_cast(p) = blk->m_nFirst;
blk->m_nFirst = static_cast((reinterpret_cast(p) - blk->m_data) / m_nUnitSize);
// 整個區塊空閒 → 回收區塊
if (blk->m_nFree * m_nUnitSize == blk->m_nSize) {
prev->m_pNext = blk->m_pNext;
if (reinterpret_cast(prev) == reinterpret_cast(m_pFirst))
m_pFirst = nullptr;
delete blk;
}
}
~MemoryPool() { while (m_pFirst) { auto nxt = m_pFirst->m_pNext; delete m_pFirst; m_pFirst = nxt; } }
};
效能測試結果 (10 萬次分配)
Without MemoryPool: ~45 ms (new/delete)
Using MemoryPool: ~3 ms (MemoryPool::Alloc/Free)
加速約 15 倍
已知問題與改進方向
- 執行緒不安全:需加 `std::mutex` 或改用 Thread-Local Pool + 全域回收。
- 對齊處理簡陋:僅 2 位元組對齊,SSE/AVX/原子操作需 16/32/64 位元組對齊,應用 `alignas` 或 `std::aligned_storage`。
- 區塊回收策略:僅當整區塊空閒才回收,長期運行可能殘留大量半滿區塊,可加入定期整理或閒置釋放機制。
- 單元大小固定:不支援變長分配,通用分配器仍需 `malloc`/`new` 或 `jemalloc`/`tcmalloc`。
- C++17 起:可用 `std::pmr::memory_resource` + `std::pmr::monotonic_buffer_resource` / `pool_resource` 標準化替代。