【C++學習】C++實現高效記憶體池
1.記憶體池簡介
記憶體池是池化技術中的一種形式,通常我們在編寫程式的時候使用new和delete關鍵字向作業系統申請記憶體。但是每一個申請記憶體和釋放記憶體的時候,都需要和作業系統的系統呼叫進行互動,並在堆中進行記憶體分配。如果操作過於頻繁,就會出現大量的記憶體碎片進而降低記憶體的分配效能。從而導致記憶體分配失敗的情況。對於記憶體申請過程而言,其實就是一次申請指標的過程,對於每一次的記憶體分配,都會消耗一次分配記憶體的時間,如果在程式開始的時候就分配好一塊合理的記憶體區域,當我們下次使用的時候,就可以直接從分配的記憶體中進行使用,降低申請記憶體分配時作業系統進行復雜排程過程的時間。
優勢:
1.從原理上比new
malloc
快 2.分配記憶體的時候
overhead
3.基本不會出現記憶體碎片
4.無需一個一個釋放記憶體,只需要釋放記憶體池就可以
2.函式實現
2.1 主函式
#include <iostream>
#include <ctime>
#include <vector>
#include <cassert>
#include "StackAlloc.h"
#include "MemoryPool.h"
using namespace std;
const int REPS = 100;
const int ELEMS = 1e8 ;
int main() {
clock_t start;
// Use standard allocator
StackAlloc<int, std::allocator<int> > stackDefault;
start = clock();
for(int reps=0; reps < REPS; ++reps) {
assert(stackDefault.empty());
for(int elems=0; elems < ELEMS; ++elems) {
stackDefault.push(elems);
}
for (int elems=0; elems < ELEMS; ++elems) {
stackDefault.pop();
}
}
std::cout << "Default Allocator Time: ";
std::cout << (double)clock() - start / CLOCKS_PER_SEC << "\n\n";
// Use memory pool
StackAlloc<int, MemoryPool<int > > stackPool;
start = clock();
for(int reps=0; reps < REPS; ++reps) {
assert(stackPool.empty());
for(int elems=0; elems < ELEMS; ++elems) {
stackPool.push(elems);
}
for(int elems=0; elems < ELEMS; ++elems) {
stackPool.pop();
}
}
std::cout << "Memory Allocator Time: ";
std::cout << (double)clock() - start / CLOCKS_PER_SEC << "\n\n";
return 0;
}
多次執行鏈式棧的入棧出棧過程,對時間效能進行比較。分別使用標準庫中的allocator和自己實現的記憶體池進行記憶體測試。對於new
和delete
,在變數分配記憶體給空間的同時對變數進行初始化,std::allocator
將變數初始化和記憶體分配隔離開。
2.2 StackAlloc.h
#ifndef OPTIMIZED_STACKALLOC_H
#define OPTIMIZED_STACKALLOC_H
#include <memory>
template <typename T>
struct StackNode_{
T data;
StackNode_* prev;
};
template <typename T, typename Alloc=std::allocator<T > >
class StackAlloc {
public:
typedef StackNode_<T> node;
typedef typename Alloc::template rebind<node>::other allocator;
StackAlloc();
~StackAlloc();
bool empty();
void clear();
void push(T element);
void pop();
void top();
private:
allocator allocator_;
Node* head_;
};
#endif //OPTIMIZED_STACKALLOC_H
1.結構體為資料值和指向上一個結點的指標。
2.template <typename T, typename Alloc=std::allocator<T > >
定義一個模板類,預設記憶體分配器是std::allocator
。
3.typedef typename Alloc::template rebind<node>::other allocator;
(1)typedef ... allocator
給記憶體分配器重新設定一個別名。
(2)typename
關鍵字是進行類別限定, 對於後面的部分在標準庫中可以被認為是靜態資料成員、靜態成員函式以及巢狀型別。使用該關鍵字說明後面的是一個型別而不是一個成員變數。
(3)rebind
如果已經為型別T建立了一個記憶體分配器allocator<T>
,如果需要按照相同的策略對另外一個型別U構建一個分配器。那麼可以使用allocator<U> = allocator<T>::rebind<U>::other
,這樣如果是我們自己定義的分配器模板allocator,就可以使用該方式,快速地實現多型別的構建過程(同族)。因為在構建T的分配器的的時候,是一個模板方式進行定義的,且rebind依賴於allocator,所以也要宣告為模板。
2.3 StackAlloc.cpp
2.4 禁用拷貝賦值
為降低程式代價,禁用拷貝賦值,僅使用移動賦值,因為記憶體池拷貝的代價巨大。
MemoryPool& operator=(const MemoryPool& mp) = delete;
MemoryPool& operator=(const MemoryPool& mp) noexcept;
C++11中的delect
表示對前面的函式宣告進行禁用。
2.5 靜態斷言與斷言
斷言方式:assert
是在執行時進行斷言,而static_assert
是在編譯階段進行斷言。
2.6 使用reinterpret_cast
在對記憶體池進行刪除的時候,通過對delete進行過載,並使用reinterpret_cast
將指標強制轉換為void *
型別。