頻繁的 new/delete 或 std::allocator 調用系統分配器會產生內存碎片、鎖競爭與調用開銷。本文實現一個定長塊 + 自由鏈表的高性能內存池 MemoryPool<T, BlockSize>,符合 STL 分配器概念,並通過鏈式棧壓測證明其分配速度遠超 std::allocator 與 std::vector。
sizeof(T)(對齊後)的槽,每槽存一個對象或作為自由鏈表節點。freeSlots_ 串聯所有空閒槽,分配/釋放為 O(1) 指針操作。currentBlock_ 當前塊,currentSlot_ 當前塊下一可用槽,lastSlot_ 當前塊末尾。template<typename T, size_t BlockSize = 4096>
class MemoryPool {
// 聯合體:槽要么存對象,要么存下一空閒槽指針
union Slot_ {
T element;
Slot_* next;
};
using Slot = Slot_;
using SlotPtr = Slot*;
using DataPtr = char*;
SlotPtr currentBlock_ = nullptr; // 塊鏈表頭
SlotPtr currentSlot_ = nullptr; // 當前塊下一可用槽
SlotPtr lastSlot_ = nullptr; // 當前塊末尾+1
SlotPtr freeSlots_ = nullptr; // 自由鏈表頭
static_assert(BlockSize >= 2 * sizeof(Slot), "BlockSize too small");
// ...
};
allocateT* allocate(size_t n = 1, const T* = 0) {
if (n != 1) throw std::bad_alloc(); // 僅支持單對象分配
if (freeSlots_) { // 優先復用自由鏈表槽
SlotPtr slot = freeSlots_;
freeSlots_ = slot->next;
return reinterpret_cast<T*>(slot);
}
if (currentSlot_ >= lastSlot_) { // 當前塊耗盡,申請新塊
DataPtr newBlock = reinterpret_cast<DataPtr>(::operator new(BlockSize));
reinterpret_cast<SlotPtr>(newBlock)->next = currentBlock_; // 塊鏈表串聯
currentBlock_ = reinterpret_cast<SlotPtr>(newBlock);
// 對齊調整:跳過塊頭 SlotPtr,按 alignof(Slot) 對齊
DataPtr body = newBlock + sizeof(SlotPtr);
uintptr_t addr = reinterpret_cast<uintptr_t>(body);
size_t padding = (alignof(Slot) - addr % alignof(Slot)) % alignof(Slot);
currentSlot_ = reinterpret_cast<SlotPtr>(body + padding);
lastSlot_ = reinterpret_cast<SlotPtr>(newBlock + BlockSize - sizeof(Slot) + 1);
}
return reinterpret_cast<T*>(currentSlot_++);
}
deallocatevoid deallocate(T* p, size_t = 1) {
if (p) {
SlotPtr slot = reinterpret_cast<SlotPtr>(p);
slot->next = freeSlots_; // 頭插自由鏈表
freeSlots_ = slot;
}
}
template<typename U, typename... Args>
void construct(U* p, Args&&... args) {
::new (p) U(std::forward<Args>(args)...); // 定位 new
}
template<typename U>
void destroy(U* p) { p->~U(); }
~MemoryPool~MemoryPool() {
SlotPtr cur = currentBlock_;
while (cur) {
SlotPtr next = cur->next;
::operator delete(cur); // 歸還系統
cur = next;
}
}
template<typename U> struct rebind { using other = MemoryPool<U, BlockSize>; };
StackAlloc<T, Alloc>template<typename T, typename Alloc = std::allocator<T>>
class StackAlloc {
struct Node { T data; Node* prev; };
using AllocTraits = std::allocator_traits<Alloc>;
using NodeAlloc = typename AllocTraits::template rebind_alloc<Node>;
NodeAlloc alloc_;
Node* head_ = nullptr;
public:
void push(const T& v) {
Node* n = AllocTraits::allocate(alloc_, 1);
AllocTraits::construct(alloc_, n, Node{v, head_});
head_ = n;
}
T pop() {
Node* n = head_;
T v = n->data;
head_ = n->prev;
AllocTraits::destroy(alloc_, n);
AllocTraits::deallocate(alloc_, n, 1);
return v;
}
bool empty() const { return !head_; }
};
#define ELEMS 10'000'000
#define REPS 100
int main() {
clock_t start;
// 1. std::allocator
StackAlloc<int, std::allocator<int>> stkStd;
start = clock();
for (int r=0; r vec;
start = clock();
for (int r=0; r
典型輸出(示例)
std::allocator: 58.23s
std::vector: 12.45s
MemoryPool: 3.17s
結論:MemoryPool 比 std::allocator 快 ~18x,比 std::vector 快 ~4x。優勢來源:無系統調用開銷、無鎖、無內存碎片、緊湊內存佈局利於缓存。
六、適用場景與局限
適用 不適用
高頻小對象分配/釋放(節點、消息、任務)
大對象、生命週期極長對象
實時系統、遊戲引擎、高頻交易
需要釋放內存給系統的場景(池析構前不歸還)
STL 容器自定義分配器
多線程共享池需自行加鎖(本實現非線程安全)
七、擴展方向
- 線程安全版:
freeSlots_ 改用無鎖棧或每線程本地緩存(TLAB)。
- 可變塊大小:按請求大小分級管理多個池。
- 統計監控:記錄分配次數、峰值使用量、碎片率。
內存池是系統級性能優化的利器,理解其原理並能正確實現 STL 兼容分配器,是 C++ 高性能開發的核心技能之一。