龜速的malloc和神速的FastMM
阿新 • • 發佈:2019-01-09
由於在Delphi專案中,要頻繁建立和釋放大量小物件,因此擔心有效率問題,於是打於GetMem.inc看看,發現FastMM對於小塊記憶體作了很多工作,它預置了一組不同大小的記憶體池,當要建立一塊記憶體時,FastMM找到大小最相近的記憶體池分配之,記憶體釋放後回收到池中。這樣的做法雖有小量記憶體浪費,但效率卻是大大提高。
我決定做一個測試,看看效率研究如何:
const cSize: Integer = 100; cNum: Integer = 10000; var N, I: Integer; P: array [0..9999] of Pointer; Fre: Int64; Count1, Count2: Int64; Time: Double; begin QueryPerformanceFrequency(Fre); QueryPerformanceCounter(Count1); for I := 0 to 1000 - 1 do begin for N := 0 to cNum - 1 do GetMem(P[N], cSize); for N := 0 to cNum - 1 do FreeMem(P[N]); end; QueryPerformanceCounter(Count2); Time := (Count2 - Count1) / Fre; Writeln(Format('Delphi2007 Release: %f', [Time])); end.
上面例子中,迴圈1000次,每次迴圈分別建立和釋放10000個100位元組的記憶體塊,執行結果如下:
Delphi2007 Release: 0.14結果非常好,這下我可以盡情使用小物件來替換記錄的工作了。
我想起C++的Malloc,不知其效率如何,於是我又將Delphi的測試程式碼轉換成C++,程式碼如下:
執行結果使我震驚,這真是龜速的malloc:LARGE_INTEGER fre; LARGE_INTEGER count1, count2; double time; QueryPerformanceFrequency(&fre); const int cSize = 100; const int cNum = 10000; void* p[cNum]; QueryPerformanceCounter(&count1); for (int i = 0; i < 1000; ++i) { for (int n = 0; n < cNum; ++n) p[n] = malloc(cSize); for (int n = 0; n < cNum; ++n) free(p[n]); } QueryPerformanceCounter(&count2); time = (count2.QuadPart - count1.QuadPart) / (double)fre.QuadPart; printf("VC2008 Release: %f\n", time);
VC2008 Release: 3.854
看來malloc並沒有對小記憶體作任何優化,所以在C++中要大量使用動態物件,是必須要小心的,否則很容易引起效能問題。找了一些替換的記憶體管理器,始終沒有辦法達到FastMM的水平,最快的也只是其一半的速度。
最後我用自己實現的一個受限的記憶體管理器測試,該管理器只能建立固定大小的記憶體塊,也是用池的方式快取記憶體塊,程式碼如下:
這次的結果很讓我滿意:LARGE_INTEGER fre; LARGE_INTEGER count1, count2; double time; QueryPerformanceFrequency(&fre); const int cSize = 100; const int cNum = 10000; void* p[cNum]; FixedAlloc myAlloc(cSize); QueryPerformanceCounter(&count1); for (int i = 0; i < 1000; ++i) { for (int n = 0; n < cNum; ++n) { //p[n] = malloc(cSize); p[n] = myAlloc.Alloc(); } for (int n = 0; n < cNum; ++n) { //free(p[n]); myAlloc.Free(p[n]); } } QueryPerformanceCounter(&count2); time = (count2.QuadPart - count1.QuadPart) / (double)fre.QuadPart; printf("VC2008 Release: %f\n", time);
VC2008 Release: 0.0806
速度比FastMM快了近一倍,但這並不表示它比FastMM好,因為FastMM更加通用,且處理了很多其他的邏輯,如果FixedAlloc做得更完善一些,或許會和FastMM接近的。因此可見,對效率很敏感的程式,使用特有的記憶體管理器是必須的,否則讓龜速的malloc來處理,一切都是龜速。
進一步想,如果開啟多執行緒判斷,FastMM的效率不知如何,於是又有下面的測試程式碼:
IsMultiThread := True;
QueryPerformanceCounter(Count1);
for I := 0 to 1000 - 1 do
begin
for N := 0 to cNum - 1 do
GetMem(P[N], cSize);
for N := 0 to cNum - 1 do
FreeMem(P[N]);
end;
QueryPerformanceCounter(Count2);
Time := (Count2 - Count1) / Fre;
Writeln(Format('Delphi2007 Release:%f', [Time]));
僅僅是把IsMultiThread開啟,效果非常明顯:Delphi2007 Release:0.41
足足比單執行緒模式慢了3倍,但是如果我自己來處理多執行緒的情況呢,結果又是如何呢:
IsMultiThread := False;
InitializeCriticalSection(CS);
QueryPerformanceCounter(Count1);
for I := 0 to 1000 - 1 do
begin
for N := 0 to cNum - 1 do
begin
EnterCriticalSection(CS);
GetMem(P[N], cSize);
LeaveCriticalSection(CS);
end;
for N := 0 to cNum - 1 do
begin
EnterCriticalSection(CS);
FreeMem(P[N]);
LeaveCriticalSection(CS);
end;
end;
QueryPerformanceCounter(Count2);
Time := (Count2 - Count1) / Fre;
Writeln(Format('Delphi2007 Release:%f', [Time]));
DeleteCriticalSection(CS);
結果很糟糕:Delphi2007 Release:0.71
FastMM並不像Delphi7那樣,用臨界區來實現多執行緒安全,因此效率要比那個方案更高一些,FastMM確實不失為一個頂級的記憶體管理器。