由於在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++,代碼如下:
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);
運行結果使我震驚,這真是龜速的malloc:
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確實不失為一個頂級的內存管理器。
摘自 colin小屋