Delphiアプリケーションのメモリリーク検出法

提供: Support
移動先: 案内検索

このドキュメントは、以前EDNに掲載されていた記事のアーカイブで、掲載されている内容は公開当初の情報となります。

概要

Delphi Win32アプリケーションのメモリリーク検出手順を紹介します。 また、Delphi 2006(BDS 2006)から導入された新しいメモリマネージャFastMMでの方法、またその効果についても検証します。

MemCheckによるメモリリークの検出

Delphiでは、これまで、MemCheckと呼ばれるフリーのメモリリーク検出ツールを使って、比較的簡単にメモリリーク箇所を発見できました。MemCheckは、Delphi 5からDelphi 2005までをサポートしており、http://v.mahon.free.fr/pro/freeware/memcheck/からダウンロードできます。


MemCheckの使用方法は、簡単です。

  • ダウンロードしたMemCheck.pasを、プロジェクトに追加します。
  • 次のようにMemChk; の呼び出しを追加します。
begin
  MemChk; // チェック開始
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
  • プロジェクトオプションの「スタックフレームの生成」をONに設定します。
  • プロジェクトオプションの「TD32デバッグ情報を含める」をONに設定します。


以上の準備作業が完了したら、プログラムを実行します。例えば次のような、メモリリークの原因となるコードを含むアプリケーションを実行します。

type
  TMyClass = class
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  p: TMyClass;
begin
  p := TMyClass.Create; // 破棄されていない
end;

すると、プログラム終了時に、次のようなログファイルが生成されます。

Thumb03000141ujpn.png

ログファイルを見れば分かるように、MemCheckは、メモリリークが発生したファイルの行番号をレポートします。これにより、簡単にメモリリーク箇所を特定し、対策を立てることができます。

Delphi 2006のFastMMメモリマネージャ

Delphi 2006(Borland Developer Studio 2006)では、MemCheckを使用することができません。その理由は、Delphi 2006のコンパイラの仕様が変更されたことと、新たに導入されたメモリマネージャFastMMによりメモリ管理の仕組みが変更されたためです。


FastMMがどのようにDelphiアプリケーションのメモリを管理し、既存のアプリケーションにどのような影響をもたらすかについては、 「Borland Developer Studio 2006の新しいメモリマネージャ」を参照してください。


FastMMは、標準でメモリリーク検出機能を備えています。アプリケーションに以下のコードを一行追加するだけで、FastMMを使用したDelphiアプリケーションは、終了時にメモリリークを報告します。

begin
  ReportMemoryLeaksOnShutdown := True; // これだけ
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

ただし、Delphi 2006に付属するFastMMは、メモリリークの検出を報告するダイアログボックスを表示するだけで、どの行でメモリリークが発生したかまでは報告しません。

Thumb03000142ujpn.png

上図は、FastMMのメモリリーク検出の結果


ただし、Delphi 2006に付属するFastMMは、メモリリークの検出を報告するダイアログボックスを表示するだけで、どの行でメモリリークが発生したかについてまでは報告しません。

フルデバッグモードのFastMM

FastMMは、https://sourceforge.net/projects/fastmm/で公開されているDelphi Win32向けのメモリマネージャです。SourceForgeのダウンロードページ https://sourceforge.net/projects/fastmm/files/FastMM%204.x%20Stable%20Releases/ からプロジェクトをダウンロードすれば、最新かつフルセットのFastMMを入手できます。

ダウンロード版FastMMに含まれるフルデバッグモードFastMMを使用すれば、メモリリーク検出で、その発生箇所を特定できる詳細なログファイルを出力できます。 フルデバッグモードのFastMMを使用するには、次のようにします。

  1. usesに ShareMemユニット を追加して、borlndmm.dll(共用メモリマネージャ) がロードされるように変更します。
    program XXXX;
    
    uses
      ShareMem, // これだけ
      Forms,
      Unit1 in 'Unit1.pas' {Form1},
    
    begin
      Application.Initialize;
      Application.CreateForm(TForm1, Form1);
      Application.Run;
    end.
    
  2. ダウンロードしたFastMMの「Debug版 BorlndMM.dll」がロードされるようにします(FastMM_FullDebugMode.dll)が実行時に必要)。
  3. プロジェクトオプションの「スタックフレームの生成」をONに設定します。
  4. プロジェクトオプションの「TD32デバッグ情報を含める」をONに設定します。
  5. アプリケーション実行時には、Borland Developer Studio 2006のIDEを起動しておきます。


以上の準備が完了したら、プログラムを実行します。プログラムを実行すると、終了時にダイアログボックスが表示されます。以上で、以下に示したような、borlndmm_MemoryManager_EventLog.txt というファイル名のログファイルが出力されます。

Thumb03000143ujpn.png

プログラム終了時に、まれにAVエラーが発生してプログラムが停止してしまうことがあります。この場合でも、ログファイルは生成されていますので、タスクマネージャやIDE上で強制終了させてかまいません。

FastMMの効果

本稿では、主にDelphiアプリケーションのメモリリーク検出手順について取り上げましたが、FastMMが具体的にどの程度メモリ管理を最適化しているかについて、市販のプロファイリングツールを使用してテストしてみたいと思います。


ここで使用するのは、Lightweight Technologies社のLTProfという49.95ドルのDelphi/C++Builderに対応したCPUプロファイラ製品です。TD32デバッグ情報を付加すれば、Delphi/C++Builderいずれのアプリケーションのプロファイリングも可能です。詳細は、https://www.lw-tech.com/ をご覧ください。


LTProfを使用するのは、簡単です。プロジェクトオプションで、「TD32デバッグ情報を含める」と「デバッグ版DCUを使う」をONに設定するだけです。アプリケーションを実行すると、図のようなプロファイリング情報を表示します。

Thumb03000144ujpn.png

上図は、LTProfのプロファイリング情報の例


Delphi 7とDelphi 2006(Borland Developer Studio 2006)での、メモリマネージャの動作を比較するために、以下のようなコードを準備しました。

procedure TForm1.Button1Click(Sender: TObject);
var
  i,j:  Integer;
  s,e:  Cardinal;
  list: TStringList;
begin
  s := GetTickCount;
  for i := 1 to 100 do
  begin
    list := TStringList.Create;
    for j := 1 to 10000 do
    begin
      list.Add(IntToStr(J));
    end;
    FreeAndNil(list);
  end;
  e := GetTickCount;

  Label1.Caption := IntToStr(e-s);
end;

ここでは、TStringListを使った重いループ処理を記述し、メモリの再割当が頻繁に発生する事態を人工的に作り出しました。


実行結果は環境にも大きく依存すると思いますが、手元の環境で実行した場合、Label1に表示されるカウンターは、Delphi 7の場合1943、Delphi 2006の場合941と、2倍以上の開きが出ました。


Delphi 7の場合、大量のTStringList.Addに伴うメモリの再配置処理(ReallocMem、Move)が発生し、これが最も大きいウェイトを占めています。IntToStrメソッド(Sysutils::CvtInt)は、メモリ再配置処理の次に時間がかかる処理でした。

Thumb03000145ujpn.png

上図は、Delphi 7のプロファイリング結果


一方、Delphi 2006では、IntToStr(Sysutils::CvtInt)が最も大きいウェイトを占めています。Sysutils::CvtIntの実装内容は、Delphi7とBDS2006とで同一であるため、この差は、単純に、TStringList.Addに伴うメモリの再配置処理が激減し、結果としてIntToStrが目立つことになった、言い換えれば、メモリの再配置処理で著しいパフォーマンス向上があったということです。

Thumb03000146ujpn.png

上図は、Delphi 2006のプロファイリング結果

まとめ

このように、フリーで入手できるツールや廉価なツールを組み合わせれば、Delphiアプリケーションの品質向上を効率的に行うことができます。アプリケーションの品質を高めるには、コーディングの定石を覚えたり、背後のアーキテクチャを理解することも重要ですが、ケアレスミスや発見しにくいミスはなかなか防ぐことはできません。このようなケースにも対応できるように、メモリリーク検出の手立てを講じておくことは重要です。