提高C++性能的编程技术笔记:单线程内存池+测试代码
頻繁地分配和回收內存會嚴重地降低程序的性能。性能降低的原因在于默認的內存管理是通用的。應用程序可能會以某種特定的方式使用內存,并且為不需要的功能付出性能上的代價。通過開發專用的內存管理器可以解決這個問題。對專用內存管理器的設計可以從多個角度考慮。我們至少可以想到兩個方面:大小和并發。
從大小的角度分為以下兩種:
(1)、固定大小:分配固定大小內存塊的內存管理器。
(2)、可變大小:分配任意大小內存塊的內存管理器。所請求分配的大小事先是未知的。
類似的,從并發的角度也分為以下兩種:
(1)、單線程:內存管理器局限在一個線程內。內存被一個線程使用,并且不越出該線程的界限。這種內存管理器不涉及相互訪問的多線程。
(2)、多線程:內存管理器被多個線程并發地使用。這種實現需要包含互斥執行的代碼段。無論什么時候,只能有一個線程在執行一個代碼段。
全局函數new()和delete():從設計上來說,默認的內存管理器是通用的。當調用全局函數new()和delete()時,我們使用的正是默認內存管理器。這兩個函數的實現不能作任何簡化假設。它們在進程范圍內管理內存。既然一個進程可能產生多個線程,new()和delete()就必須能夠在多線程環境中運行。而且,每次請求分配的內存大小是不同的。這種靈活性以速度為代價。所做的計算越多,消耗的周期就越多。
通常情況下,客戶端代碼不需要全局函數new()和delete()的全部功能。它可能只(或通常)需要特定大小的內存塊。客戶端代碼很可能在單線程環境中運行,在這種環境中并不真正需要默認的new()和delete()所提供的并發保護。這樣的話,使用這兩個函數的全部功能就會浪費CPU周期。通過調整內存分配策略來更好地匹配特定需求,可以明顯地提高性能。
靈活性以速度的降低為代價.隨著內存管理的功能和靈活性的增強,執行速度將降低.
全局內存管理器(由new()和delete()執行)是通用的,因此代價高。
專用內存管理器比全局內存管理器快一個數量級以上。
如果主要分配固定大小的內存塊,專用的固定大小內存管理器將明顯地提升性能。
如果主要分配限于單線程的內存塊,那么內存管理器也會有類似的性能提高。由于省去了全局函數new()和delete()必須處理的并發問題,單線程內存管理器的性能有所提高。
以下是測試代碼(single_threaded_memory_pool.cpp):
#include "single_threaded_memory_pool.hpp"
#include <iostream>
#include <chrono>
#include <string>namespace single_threaded_memory_pool_ {// reference: 《提高C++性能的編程技術》:第六章:單線程內存池class Rational1 {
public:Rational1(int a = 0, int b = 1) : n(a), d(b) {}
private:int n; // 分子int d; // 分母
}; // class Rational1// 專用Rational2內存管理器
// 為避免頻繁地使用內存管理器,Rational2類要維護一個預分配的Rational2對象的靜態連接列表,該列表列出空閑的可用對象.
// 當需要Rational2對象時,可以從空閑列表中取出一個,使用后再把它放回空閑列表以便今后分配.
// 聲明一個輔助結構來連接空閑列表的相鄰元素
class NextOnFreeList {
public:NextOnFreeList* next;
}; // class NextOnFreeList// 空閑列表被聲明為一個由NextOnFreeList元素組成的列表
class Rational2 {
public:Rational2(int a = 0, int b = 1) : n(a),d(b) {}inline void* operator new(size_t size);inline void operator delete(void* doomed, size_t size);static void newMemPool() { expandTheFreeList(); }static void deleteMemPool();private:static NextOnFreeList* freeList; // Rational2對象的空閑列表static void expandTheFreeList();enum { EXPANSION_SIZE = 32};int n; // 分子int d; // 分母
}; // class Rational2NextOnFreeList* Rational2::freeList = nullptr;// new()在空閑列表中分配一個Rational2對象.如果列表為空,則擴展列表
inline void* Rational2::operator new(size_t size)
{if (nullptr == freeList) { // 如果列表為空,則將其填滿expandTheFreeList();}NextOnFreeList* head = freeList;freeList = head->next;return head;
}// delete()把Rational2對象直接添加到空閑列表的頭部,以返回Rational2對象
inline void Rational2::operator delete(void* doomed, size_t size)
{NextOnFreeList* head = static_cast<NextOnFreeList*>(doomed);head->next = freeList;freeList = head;
}// 當空閑列表用完后,需要從堆上分配更多的Rational2對象.
// Rational2和NextOnFreeList之間的類型轉換是有危險的,必須確保空閑列表的元素足夠大以支持任意一種類型
// 當我們用Rational2對象填充空閑列表時,要記得比較Rational2和NextOnFreeList的大小,并且分配較大的那一個
void Rational2::expandTheFreeList()
{// 本質上,expandTheFreeList的實現并不是最優的.因為空閑列表中每增加一個元素,就調用一次new. 如果只調用一次new// 獲得一大塊內存,然后把它切分給多個元素,這樣會更高效。孤立地看,這種想法很正確。然而我們創建內存管理器時,// 認為它不會頻繁擴展和收縮,否則必須重新查看代碼實現并修正它// 我們必須分配足夠大的對象以包含下一個指針size_t size = sizeof(Rational2) > sizeof(NextOnFreeList*) ? sizeof(Rational2) : sizeof(NextOnFreeList*);NextOnFreeList* runner = static_cast<NextOnFreeList*>((void*)new char[size]);freeList = runner;for (int i = 0; i < EXPANSION_SIZE; ++i) {runner->next = static_cast<NextOnFreeList*>((void*)new char[size]);runner = runner->next;}runner->next = nullptr;
}void Rational2::deleteMemPool()
{NextOnFreeList* nextPtr;for (nextPtr = freeList; nextPtr != nullptr; nextPtr = freeList) {freeList = freeList->next;delete [] nextPtr;}
}///
// 固定大小對象的內存池: 觀察Rational2內存管理器的實現,會很清楚地發現內存管理邏輯實際上獨立于特定的Rational2類.
// 它唯一依賴的是類對象的大小----這正是用模板實現內存池的原因
template<class T>
class MemoryPool1 {
public:MemoryPool1(size_t size = EXPANSION_SIZE);~MemoryPool1();// 從空閑列表中分配T元素inline void* alloc(size_t size);// 返回T元素到空閑列表中inline void free(void* someElement);private:// 空閑列表的下一元素MemoryPool1<T>* next;// 如果空閑列表為空,按該大小擴展它enum { EXPANSION_SIZE = 32 };// 添加空閑元素至空閑列表void expandTheFreeList(int howMany = EXPANSION_SIZE);
};// 構造函數初始化空閑列表,參數size指定空閑列表的初始化長度
template<class T>
MemoryPool1<T>::MemoryPool1(size_t size)
{expandTheFreeList(size);
}// 析構函數遍歷空閑列表并且刪除所有元素
template<class T>
MemoryPool1<T>::~MemoryPool1()
{MemoryPool1<T>* nextPtr = next;for (nextPtr = next; nextPtr != nullptr; nextPtr = next) {next = next->next;delete [] static_cast<char*>(static_cast<void*>(nextPtr));}
}// alloc函數為T元素分配足夠大的空間,如果空閑列表用盡,則調用expandThrFreeList函數來擴充它
template<class T>
inline void* MemoryPool1<T>::alloc(size_t size)
{if (!next) {expandTheFreeList();}MemoryPool1<T>* head = next;next = head->next;return head;
}// free函數把T元素放回空閑列表,以此來釋放它
template<class T>
inline void MemoryPool1<T>::free(void* doomed)
{MemoryPool1<T>* head = static_cast<MemoryPool1<T>*>(doomed);head->next = next;next = head;
}// expandTheFreeList函數用來向空閑列表添加新元素,首先從堆上分配新元素,然后把它們連接到列表中
// 該函數在空閑列表用盡時被調用
template<class T>
void MemoryPool1<T>::expandTheFreeList(int howMany)
{// 必須分配足夠大的對象以包含下一個指針size_t size = sizeof(T) > sizeof(MemoryPool1<T>*) ? sizeof(T) : sizeof(MemoryPool1<T>*);MemoryPool1<T>* runner = static_cast<MemoryPool1<T>*>((void*)(new char[size]));next = runner;for (int i = 0; i < howMany; ++i) {runner->next = static_cast<MemoryPool1<T>*>((void*)(new char[size]));runner = runner->next;}runner->next = nullptr;
}// Rational3類不再需要維護它自己的空閑列表,這項任務委托給了MemoryPool1類
class Rational3 {
public:Rational3(int a = 0, int b = 1) : n(a),d(b) {}void* operator new(size_t size) { return memPool->alloc(size); }void operator delete(void* doomed, size_t size) { memPool->free(doomed); }static void newMemPool() { memPool = new MemoryPool1<Rational3>; }static void deleteMemPool() { delete memPool; }private:int n; // 分子int d; // 分母static MemoryPool1<Rational3>* memPool;
};MemoryPool1<Rational3>* Rational3::memPool = nullptr;/
// 單線程可變大小內存管理器:
// MemoryChunk類取代之前版本中使用的NextOnFreeList類,它用來把不同大小的內存塊連接起來形成塊序列
class MemoryChunk {
public:MemoryChunk(MemoryChunk* nextChunk, size_t chunkSize);// 析構函數釋放構造函數獲得的內存空間~MemoryChunk() { delete [] mem; }inline void* alloc(size_t size);inline void free(void* someElement);// 指向列表下一內存塊的指針MemoryChunk* nextMemChunk() { return next; }// 當前內存塊剩余空間大小size_t spaceAvailable() { return chunkSize - bytesAlreadyAllocated; }// 這是一個內存塊的默認大小enum { DEFAULT_CHUNK_SIZE = 4096 };private:MemoryChunk* next;void* mem;// 一個內存塊的默認大小size_t chunkSize;// 當前內存塊中已分配的字節數size_t bytesAlreadyAllocated;
};// 構造函數首先確定內存塊的適當大小,然后根據這個大小從堆上分配私有存儲空間
// MemoryChunk將next成員指向輸入參數nextChunk, nextChunk是列表先前的頭部
MemoryChunk::MemoryChunk(MemoryChunk* nextChunk, size_t reqSize)
{chunkSize = (reqSize > DEFAULT_CHUNK_SIZE) ? reqSize : DEFAULT_CHUNK_SIZE;next = nextChunk;bytesAlreadyAllocated = 0;mem = new char[chunkSize];
}// alloc函數處理內存分配請求,它返回一個指針,該指針指向mem所指向的MemoryChunk私有存儲空間中的可用空間。
// 該函數通過更新該塊中已分配的字節數來記錄可用空間的大小
void* MemoryChunk::alloc(size_t requestSize)
{void* addr = static_cast<void*>(static_cast<char*>(mem) + bytesAlreadyAllocated);bytesAlreadyAllocated += requestSize;return addr;
}// 在該實現中,不用擔心空閑內存段的釋放。當對象被刪除后,整個內存塊將被釋放并且返回到堆上
inline void MemoryChunk::free(void* doomed)
{
}// MemoryChunk只是一個輔助類,ByteMemoryPoll類用它來實現可變大小的內存管理
class ByteMemoryPool {
public:ByteMemoryPool(size_t initSize = MemoryChunk::DEFAULT_CHUNK_SIZE);~ByteMemoryPool();// 從私有內存池分配內存inline void* alloc(size_t size);// 釋放先前從內存池中分配的內存inline void free(void* someElement);private:// 內存塊列表,它是我們的私有存儲空間MemoryChunk* listOfMemoryChunks = nullptr;// 向我們的私有存儲空間添加一個內存塊void expandStorage(size_t reqSize);
};// 雖然內存塊列表可能包含多個塊,但只有第一塊擁有可用于分配的內存。其它塊表示已分配的內存。
// 列表的首個元素是唯一能夠分配可以內存的塊。// 構造函數接收initSize參數來設定一個內存塊的大小,即構造函數借此來設置單個內存塊的大小。
// expandStorage方法使listOfMemoryChunks指向一個已分配的MemoryChunk對象
// 創建ByteMemoryPool對象,生成私有存儲空間
ByteMemoryPool::ByteMemoryPool(size_t initSize)
{expandStorage(initSize);
}// 析構函數遍歷內存塊列表并且刪除它們
ByteMemoryPool::~ByteMemoryPool()
{MemoryChunk* memChunk = listOfMemoryChunks;while (memChunk) {listOfMemoryChunks = memChunk->nextMemChunk();delete memChunk;memChunk = listOfMemoryChunks;}
}// alloc函數確保有足夠的可用空間,而把分配任務托付給列表頭的MemoryChunk
void* ByteMemoryPool::alloc(size_t requestSize)
{size_t space = listOfMemoryChunks->spaceAvailable();if (space < requestSize) {expandStorage(requestSize);}return listOfMemoryChunks->alloc(requestSize);
}// 釋放之前分配的內存的任務被委派給列表頭部的MemoryChunk來完成
// MemoryChunk::free不做任何事情,因為ByteMemoryPool的實現不會重用之前分配的內存。如果需要更多內存,
// 我們將創建新的內存塊以便今后分配使用。在內存池被銷毀時,內存釋放回堆中。ByteMemoryPool析構函數
// 釋放所有的內存塊到堆中
inline void ByteMemoryPool::free(void* doomed)
{listOfMemoryChunks->free(doomed);
}// 若遇到內存塊用盡這種不太可能的情況,我們通過創建新的內存塊并把它添加到內存塊列表的頭部來擴展它
void ByteMemoryPool::expandStorage(size_t reqSize)
{listOfMemoryChunks = new MemoryChunk(listOfMemoryChunks, reqSize);
}class Rational4 {
public:Rational4(int a = 0, int b = 1) : n(a),d(b) {}void* operator new(size_t size) { return memPool->alloc(size); }void operator delete(void* doomed, size_t size) { memPool->free(doomed); }static void newMemPool() { memPool = new ByteMemoryPool; }static void deleteMemPool() { delete memPool; }private:int n; // 分子int d; // 分母static ByteMemoryPool* memPool;
};ByteMemoryPool* Rational4::memPool = nullptr;/
int test_single_threaded_memory_pool_1()
{using namespace std::chrono;high_resolution_clock::time_point time_start, time_end;const int cycle_number1{10000}, cycle_number2{1000};{ // 測試全局函數new()和delete()的基準性能Rational1* array[cycle_number2];time_start = high_resolution_clock::now();for (int j =0; j < cycle_number1; ++j) {for (int i =0; i < cycle_number2; ++i) {array[i] = new Rational1(i);}for (int i = 0; i < cycle_number2; ++i) {delete array[i];} }time_end = high_resolution_clock::now();fprintf(stdout, "global function new/delete time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());
}{ // 專用Rational2內存管理器測試Rational2* array[cycle_number2];time_start = high_resolution_clock::now();Rational2::newMemPool();for (int j = 0; j < cycle_number1; ++j) {for (int i = 0; i < cycle_number2; ++i) {array[i] = new Rational2(i);}for (int i = 0; i < cycle_number2; ++i) {delete array[i];}}Rational2::deleteMemPool();time_end = high_resolution_clock::now();fprintf(stdout, "specialized rational2 memory manager time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());
}{ // 固定大小對象的內存池測試Rational3* array[cycle_number2];time_start = high_resolution_clock::now();Rational3::newMemPool();for (int j = 0; j < cycle_number1; ++j) {for (int i = 0; i < cycle_number2; ++i) {array[i] = new Rational3(i);}for (int i = 0; i < cycle_number2; ++i) {delete array[i];}}Rational3::deleteMemPool();time_end = high_resolution_clock::now();fprintf(stdout, "fixed-size object memory pool time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());
}{ // 單線程可變大小內存管理器測試Rational4* array[cycle_number2];time_start = high_resolution_clock::now();Rational4::newMemPool();for (int j = 0; j < cycle_number1; ++j) {for (int i = 0; i < cycle_number2; ++i) {array[i] = new Rational4(i);}for (int i = 0; i < cycle_number2; ++i) {delete array[i];}}Rational4::deleteMemPool();time_end = high_resolution_clock::now();fprintf(stdout, "single-threaded variable-size memory manager time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count()); }return 0;
}} // namespace single_threaded_memory_pool_
執行結果如下:
GitHub:?https://github.com/fengbingchun/Messy_Test?
總結
以上是生活随笔為你收集整理的提高C++性能的编程技术笔记:单线程内存池+测试代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 提高C++性能的编程技术笔记:临时对象+
- 下一篇: 提高C++性能的编程技术笔记:多线程内存