目錄結構
- 記憶體池簡介
- 介紹分析記憶體池改進及所使用技術
- 如何配合STL容器
- 效能測試
- 如何使用
記憶體池簡介:
關於記憶體池的介紹,好文很多(這裡,這裡,這裡,還有這裡…)。在如今的PC機器上,記憶體池的作用也沒有那麼明顯了,作業系統對記憶體的管理已經相當不錯。但,為什麼還需要記憶體池呢?兩點:1. 減少記憶體管理負擔,提升效能。(比如,在進行密集型演算法前申請足夠記憶體記憶體,然後使用,最後統一釋放) 2.減少分頁錯誤(分頁錯誤是由虛擬記憶體與實體記憶體交換導致的),減少記憶體片段,提高效能。
介紹分析記憶體池改進及所使用技術:
該套記憶體池組件是由SGI記憶體改造而來,其原理請參考《STL源碼剖析》,或者參閱這裡,借用一幅圖來說明
在此基礎上,我增加了幾個重要的改進:
- 增加模板參數是否為多線程,根據該參數判斷是否需要使用Lock及變數是否需要volatile修飾。
- 增加模板參數分配區塊上限,預設為256。
- 增加模板參數分配記憶體策略,提供三種分配方式:VirtualAlloc、HeapAlloc、malloc。
- 增加對已申請記憶體管理,集中釋放。
在此,講下記憶體配置策略的選擇。
// Win32 上分配記憶體方式
struct VirtualAllocateTraits
{
static void *Allocate(size_t size)
{
// 將指定的記憶體頁面始終儲存在實體記憶體上,不許它交換到磁碟頁檔案中
void *p = ::VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
::VirtualLock(p, size);
return p;
}
static void Deallocate(void *p, size_t size)
{
::VirtualUnlock(p, size);
::VirtualFree(p, size, MEM_RELEASE);
}
};
struct HeapAllocateTraits
{
static HANDLE GetHeap()
{
static HANDLE heap = NULL;
if ( heap == NULL )
{
heap = ::HeapCreate(0, 0, 0);
// 設定低片段堆
ULONG uHeapFragValue = 2;
::HeapSetInformation(heap, HeapCompatibilityInformation, &uHeapFragValue, sizeof(ULONG));
}
return heap;
}
static void *Allocate(size_t size)
{
return ::HeapAlloc(GetHeap(), HEAP_ZERO_MEMORY, size);;
}
static void Deallocate(void *p, size_t /* size*/)
{
::HeapFree(GetHeap(), 0, p);
}
};
struct MallocAllocateTraits
{
static void *Allocate(size_t size)
{
return std::malloc(size);
}
static void Deallocate(void *p, size_t)
{
return std::free(p);
}
};
預設時採用MallocAllocateTraits,即使用malloc來分配記憶體。如果是需要對記憶體的進一步控制,比如使用非同步讀取檔案時,需要使用鎖定的記憶體;在網路層中往往需要鎖定記憶體時就可以選擇VirtualAllocTraits,或者可以自行定製,僅僅需要滿足Allocate和Deallocate介面約束即可。
如何搭配STL容器: 首先,STL提出了allocator的概念,把資料的與記憶體配置釋放分離即記憶體管理從容器的實現獨立出來,這是一個很了不起的革命性變化。請看相關討論(這裡, 這裡,這裡)。然後,為了使該記憶體池符合allocator的介面,需要一個adapter—SGIAllocator.樣本:
typedef std::vector<int, SGIAllocator<int, false>> SVector;
typedef std::list<int, SGIAllocator<int, false, 1024>> SList;
效能測試:
分別以std::new,std::allocator和boost::pool作為參照物進行比較。(loki的不想測試)在固定迴圈10000次,按8byte、64byte、1K、4K為大小進行了4次測試。每次測試間隔走5次,取平均值。一下為測試案例的一部分:
for(DWORD i = 0; i != dwCount; ++i)
{
arr[i] = new char[dwSize];
memset(arr[i], i, dwSize);
if( i % 2 == 0 )
delete arr[i];
}
for(DWORD i = 0; i != dwCount; ++i)
{
if( i % 2 != 0 )
delete arr[i];
}
效能比較測試如下,以std::new為基準 很明顯,在小資料時,申請釋放次數越多,記憶體池越佔優勢。當資料較大時,記憶體池與標準分配方式的差距會更小。但是,作為服務端程式來講,鼓勵使用記憶體池來進行記憶體管理。呵呵,不言自明~
記憶體池使用方式:
// SGIMemoryPool,單線程不加鎖 malloc分配策略
typedef Allocator<char, SGIMemoryPool<false, dwSize> > SGIAllocT;
SGIAllocT alloc2;
// SGIMemoryPool,單線程不加鎖 malloc分配策略
dwLast = 0;
{
QPerformanceTimer perf(dwLast);
for(DWORD i = 0; i != dwCount; ++i)
{
arr[i] = alloc2.Allocate(dwSize);
memset(arr[i], i, dwSize);
if( i % 2 == 0 )
alloc2.Deallocate(arr[i], dwSize);
}
for(DWORD i = 0; i != dwCount; ++i)
{
if( i % 2 != 0 )
alloc2.Deallocate(arr[i], dwSize);
}
}
cout << "SGIAllocT: " << dwLast << endl;
不足:
該記憶體池同樣沒有提供釋放某一部分記憶體的介面,遺憾~~
歡迎喜歡的朋友進行下載試用,如果有更好的建議請告訴我,我一定會改進,謝謝!
下載:
目前就不放到Google Code上,還是在CSDN老地方下載吧。猛擊這裡