1. 程式人生 > >龜速的malloc和神速的FastMM

龜速的malloc和神速的FastMM

由於在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確實不失為一個頂級的記憶體管理器。