【原始碼剖析】MemoryPool —— 簡單高效的記憶體池 allocator 實現
什麼是記憶體池?什麼是 C++ 的 allocator?
記憶體池簡單說,是為了減少頻繁使用 malloc/free new/delete 等系統呼叫而造成的效能損耗而設計的。當我們的程式需要頻繁地申請和釋放記憶體時,頻繁地使用記憶體管理的系統呼叫可能會造成效能的瓶頸,嗯,是可能,畢竟作業系統的設計也不是蓋的(麻麻說把話說太滿會被打臉的(⊙v⊙))。記憶體池的思想是申請較大的一塊記憶體(不夠時繼續申請),之後把記憶體管理放在應用層執行,減少系統呼叫的開銷。
那麼,allocator 呢?它默默的工作在 C++ 所有容器的記憶體分配上。默默貼幾個連結吧:
http://www.cnblogs.com/wpcockroach/archive/2012/05/10/2493564.html
http://blog.csdn.net/justaipanda/article/details/7790355
http://www.cplusplus.com/reference/memory/allocator/
http://www.cplusplus.com/reference/memory/allocator_traits/
當你對 allocator 有基本的瞭解之後,再看這個專案應該會有恍然大悟的感覺,因為這個記憶體池是以一個 allocator 的標準來實現的。一開始不明白專案裡很多函式的定義是為了什麼,結果初步瞭解了 allocator 後才知道大部分是標準介面。這樣一個 memory pool allocator 可以與大多數 STL 容器相容,也可以應用於你自定義的類。像作者給出的例子 —— test.cpp, 是用一個基於自己寫的 stack 來做 memory pool allocator 和 std::allocator 效能的對比 —— 最後當然是 memory pool allocator 更優。
專案:
Github:MemoryPool
基本使用:
因為這是一個 allocator 類,所以所有使用 std::allocator 的地方都可以使用這個 MemoryPool。在專案的 test.cpp 中,MemoryPool 作為 allocator 用於 StackAlloc(作者實現的 demo 類) 的記憶體管理類。定義如下:
其次,你也可以將其直接作為任一型別的記憶體池,用 newElement 建立新元素,deleteElement 釋放元素,就像 new/delete 一樣。用下面的例子和 new/delete 做對比:StackAlloc<int, MemoryPool<int> > stackPool;
#include <iostream>
#include <cassert>
#include <time.h>
#include <vector>
#include <stack>
#include "MemoryPool.h"
using namespace std;
/* Adjust these values depending on how much you trust your computer */
#define ELEMS 1000000
#define REPS 50
int main()
{
clock_t start;
MemoryPool<size_t> pool;
start = clock();
for(int i = 0;i < REPS;++i)
{
for(int j = 0;j< ELEMS;++j)
{
// 建立元素
size_t* x = pool.newElement();
// 釋放元素
pool.deleteElement(x);
}
}
std::cout << "MemoryPool Time: ";
std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
start = clock();
for(int i = 0;i < REPS;++i)
{
for(int j = 0;j< ELEMS;++j)
{
size_t* x = new size_t;
delete x;
}
}
std::cout << "new/delete Time: ";
std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
return 0;
}
執行的結果是:
MemoryPool Time: 1.93389
new/delete Time: 4.64903
嗯,記憶體池快了一倍多。如果是自定義的類的話這個差距應該還會更大一點。
程式碼分析:
專案的實現有 C++11 和 C++98 兩個版本,C++11 版本似乎更加高效,不過個人 C++11 瞭解不多,就以 C++98 版本來分析吧。
主要函式:
allocate 分配一個物件所需的記憶體空間
deallocate 釋放一個物件的記憶體(歸還給記憶體池,不是給作業系統)
construct 在已申請的記憶體空間上構造物件
destroy 析構物件
newElement 從記憶體池申請一個物件所需空間,並呼叫物件的建構函式
deleteElement 析構物件,將記憶體空間歸還給記憶體池
allocateBlock 從作業系統申請一整塊記憶體放入記憶體池
關鍵知識點:
理解專案的關鍵在於理解 placement new 和 union 的用法.
placement new: http://blog.csdn.net/zhangxinrun/article/details/5940019
union:http://www.cnblogs.com/BeyondTechnology/archive/2010/09/19/1831293.html
關於 union 的使用覺得好巧妙,這是相應的定義:
union Slot_ {
value_type element;
Slot_* next;
};
Slot_ 在建立物件的時候存放物件的值,當這個物件被釋放時這塊記憶體作為一個 Slot_* 指標放入 free 的連結串列。所以 Slot_ 既可以用來存放物件,又可以用來構造連結串列。
工作原理:
記憶體池是一個一個的 block 以連結串列的形式連線起來,每一個 block 是一塊大的記憶體,當記憶體池的記憶體不足的時候,就會向作業系統申請新的 block 加入連結串列。還有一個 freeSlots_ 的連結串列,連結串列裡面的每一項都是物件被釋放後歸還給記憶體池的空間,記憶體池剛建立時 freeSlots_ 是空的,之後隨著使用者建立物件,再將物件釋放掉,這時候要把記憶體歸還給記憶體池,怎麼歸還呢?就是把指向這個物件的記憶體的指標加到 freeSlots_ 連結串列的前面(前插)。
使用者在建立物件的時候,先檢查 freeSlots_ 是否為空,不為空的時候直接取出一項作為分配出的空間。否則就在當前 block 內取出一個 Slot_ 大小的記憶體分配出去,如果 block 裡面的記憶體已經使用完了呢?就向作業系統申請一個新的 block。
記憶體池工作期間的記憶體只會增長,不釋放給作業系統。直到記憶體池銷燬的時候,才把所有的 block delete 掉。
註釋原始碼:
標頭檔案:
/*-
* Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H
#include <limits.h>
#include <stddef.h>
template <typename T, size_t BlockSize = 4096>
class MemoryPool
{
public:
/* Member types */
typedef T value_type; // T 的 value 型別
typedef T* pointer; // T 的 指標型別
typedef T& reference; // T 的引用型別
typedef const T* const_pointer; // T 的 const 指標型別
typedef const T& const_reference; // T 的 const 引用型別
typedef size_t size_type; // size_t 型別
typedef ptrdiff_t difference_type; // 指標減法結果型別
template <typename U> struct rebind {
typedef MemoryPool<U> other;
};
/* Member functions */
/* 建構函式 */
MemoryPool() throw();
MemoryPool(const MemoryPool& memoryPool) throw();
template <class U> MemoryPool(const MemoryPool<U>& memoryPool) throw();
/* 解構函式 */
~MemoryPool() throw();
/* 元素取址 */
pointer address(reference x) const throw();
const_pointer address(const_reference x) const throw();
// Can only allocate one object at a time. n and hint are ignored
// 分配和收回一個元素的記憶體空間
pointer allocate(size_type n = 1, const_pointer hint = 0);
void deallocate(pointer p, size_type n = 1);
// 可達到的最多元素數
size_type max_size() const throw();
// 基於記憶體池的元素構造和析構
void construct(pointer p, const_reference val);
void destroy(pointer p);
// 自帶申請記憶體和釋放記憶體的構造和析構
pointer newElement(const_reference val);
void deleteElement(pointer p);
private:
// union 結構體,用於存放元素或 next 指標
union Slot_ {
value_type element;
Slot_* next;
};
typedef char* data_pointer_; // char* 指標,主要用於指向記憶體首地址
typedef Slot_ slot_type_; // Slot_ 值型別
typedef Slot_* slot_pointer_; // Slot_* 指標型別
slot_pointer_ currentBlock_; // 記憶體塊連結串列的頭指標
slot_pointer_ currentSlot_; // 元素連結串列的頭指標
slot_pointer_ lastSlot_; // 可存放元素的最後指標
slot_pointer_ freeSlots_; // 元素構造後釋放掉的記憶體連結串列頭指標
size_type padPointer(data_pointer_ p, size_type align) const throw(); // 計算對齊所需空間
void allocateBlock(); // 申請記憶體塊放進記憶體池
/*
static_assert(BlockSize >= 2 * sizeof(slot_type_), "BlockSize too small.");
*/
};
#include "MemoryPool.tcc"
#endif // MEMORY_POOL_H
實現檔案:
/*-
* Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MEMORY_BLOCK_TCC
#define MEMORY_BLOCK_TCC
// 計算對齊所需補的空間
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align)
const throw()
{
size_t result = reinterpret_cast<size_t>(p);
return ((align - result) % align);
}
/* 建構函式,所有成員初始化 */
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool()
throw()
{
currentBlock_ = 0;
currentSlot_ = 0;
lastSlot_ = 0;
freeSlots_ = 0;
}
/* 複製建構函式,呼叫 MemoryPool 初始化*/
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool& memoryPool)
throw()
{
MemoryPool();
}
/* 複製建構函式,呼叫 MemoryPool 初始化*/
template <typename T, size_t BlockSize>
template<class U>
MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool<U>& memoryPool)
throw()
{
MemoryPool();
}
/* 解構函式,把記憶體池中所有 block delete 掉 */
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::~MemoryPool()
throw()
{
slot_pointer_ curr = currentBlock_;
while (curr != 0) {
slot_pointer_ prev = curr->next;
// 轉化為 void 指標,是因為 void 型別不需要呼叫解構函式,只釋放空間
operator delete(reinterpret_cast<void*>(curr));
curr = prev;
}
}
/* 返回地址 */
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::address(reference x)
const throw()
{
return &x;
}
/* 返回地址的 const 過載*/
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::const_pointer
MemoryPool<T, BlockSize>::address(const_reference x)
const throw()
{
return &x;
}
// 申請一塊空閒的 block 放進記憶體池
template <typename T, size_t BlockSize>
void
MemoryPool<T, BlockSize>::allocateBlock()
{
// Allocate space for the new block and store a pointer to the previous one
// operator new 申請對應大小記憶體,返回 void* 指標
data_pointer_ newBlock = reinterpret_cast<data_pointer_>
(operator new(BlockSize));
// 原來的 block 鏈頭接到 newblock
reinterpret_cast<slot_pointer_>(newBlock)->next = currentBlock_;
// 新的 currentblock_
currentBlock_ = reinterpret_cast<slot_pointer_>(newBlock);
// Pad block body to staisfy the alignment requirements for elements
data_pointer_ body = newBlock + sizeof(slot_pointer_);
// 計算為了對齊應該空出多少位置
size_type bodyPadding = padPointer(body, sizeof(slot_type_));
// currentslot_ 為該 block 開始的地方加上 bodypadding 個 char* 空間
currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
// 計算最後一個能放置 slot_type_ 的位置
lastSlot_ = reinterpret_cast<slot_pointer_>
(newBlock + BlockSize - sizeof(slot_type_) + 1);
}
// 返回指向分配新元素所需記憶體的指標
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::allocate(size_type, const_pointer)
{
// 如果 freeSlots_ 非空,就在 freeSlots_ 中取記憶體
if (freeSlots_ != 0) {
pointer result = reinterpret_cast<pointer>(freeSlots_);
// 更新 freeSlots_
freeSlots_ = freeSlots_->next;
return result;
}
else {
if (currentSlot_ >= lastSlot_)
// 之前申請的記憶體用完了,分配新的 block
allocateBlock();
// 從分配的 block 中劃分出去
return reinterpret_cast<pointer>(currentSlot_++);
}
}
// 將元素記憶體歸還給 free 記憶體連結串列
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deallocate(pointer p, size_type)
{
if (p != 0) {
// 轉換成 slot_pointer_ 指標,next 指向 freeSlots_ 連結串列
reinterpret_cast<slot_pointer_>(p)->next = freeSlots_;
// 新的 freeSlots_ 頭為 p
freeSlots_ = reinterpret_cast<slot_pointer_>(p);
}
}
// 計算可達到的最大元素上限數
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::max_size()
const throw()
{
size_type maxBlocks = -1 / BlockSize;
return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
}
// 在已分配記憶體上構造物件
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::construct(pointer p, const_reference val)
{
// placement new 用法,在已有記憶體上構造物件,呼叫 T 的複製建構函式,
new (p) value_type (val);
}
// 銷燬物件
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::destroy(pointer p)
{
// placement new 中需要手動呼叫元素 T 的解構函式
p->~value_type();
}
// 建立新元素
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::newElement(const_reference val)
{
// 申請記憶體
pointer result = allocate();
// 在記憶體上構造物件
construct(result, val);
return result;
}
// 刪除元素
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deleteElement(pointer p)
{
if (p != 0) {
// placement new 中需要手動呼叫元素 T 的解構函式
p->~value_type();
// 歸還記憶體
deallocate(p);
}
}
#endif // MEMORY_BLOCK_TCC