.NET アプリケーションのパフォーマンスとスケーラビリティの向上
J.D. Meier, Srinath Vasireddy, Ashish Babbar, and Alex Mackman
Microsoft Corporation
May 2004
日本語版最終更新日 2005 年 11 月 30 日
要約: この How To 情報では、CLR プロファイラ ツールを使用して、アプリケーションのメモリ アロケーション プロファイルを調査する方法を説明します。CLR プロファイラを使用すれば、メモリ リークおよび過剰もしくは効率の悪いガベージ コレクションなどのメモリ関連の問題を引き起こすコードを特定できます。
対象製品
目次
概要
CLR プロファイラをダウンロードする
理解しておくべきこと
アプリケーションのプロファイリング
ASP.NET アプリケーションのプロファイリングを行う
ガベージ コレクションの代表的な問題を特定する
アプリケーションのメモリ割り当て場所を特定する
アプリケーションの割り当てプロファイルを分析する
サンプル: ProfilerSample1
サンプル: ProfilerSample2
補足資料
概要
CLR プロファイラを使うことで、プロセスのマネージ ヒープに注目し、ガベージ コレクタの動作をチェックすることができます。このツールで用意されたさまざまなビューを使用して、アプリケーションの実行、割り当て、メモリ消費に関する有益な情報を入手できます。
CLR プロファイラは、問題解析の出発点にはなりません。むしろ、問題のあるコードを特定・分離し、メモリ リークを突き止めるためのものです。CLR プロファイラを使用することで、多すぎるメモリを割り当て、ガベージ コレクションを何度も引き起こし、さらにメモリを長時間保持してしまうようなコードを特定することが可能です。
メモ: CLR プロファイラは、アプリケーションのパフォーマンスを通常よりも著しく (10 ~ 100 倍程度) 低下させる、非常に負荷の大きなツールです。このツールは、運用環境で使用するようには設計されていません。
CLR プロファイラをダウンロードする
CLR プロファイラは、自己解凍ファイルとしてダウンロードできます。解凍ファイルには、ソース コードと実行ファイル (CLRProfiler.exe) が含まれています。ダウンロード ファイルには、CLR プロファイラに関する詳細情報を記載する包括的なドキュメントも含まれています。
CLR プロファイラをダウンロードする
理解しておくべきこと
CLR プロファイラを使用する主目的は、アプリケーションが、ガベージ コレクトされるマネージ ヒープとどのように関連しているかを理解することです。調査が可能な重要事項には、以下の項目があります。
- 何が、何をマネージ ヒープ上に割り当てているか?
- マネージ ヒープ上で回収されずに残るのはどのオブジェクトか?
- 何がオブジェクトを保持しているか?
- アプリケーションのライフタイムの間、ガベージ コレクタがどのようなことをしているか?
プロファイリングの結果は、ログ ファイルに保存されます。これらのファイルは、CLR プロファイラの対応グラフを表示する表示メニューによりさまざまな方法で表示されます。表 1 に、役立つビューを示します。
表 1: CLR プロファイラのビュー
| ビュー | 説明 |
Histogram Allocated Types (割り当てられた型のヒストグラム) | アプリケーションのライフタイムの間、どのオブジェクト型が割り当てられるかを (アロケーション サイズで) 示す高レベルのビューを提供します。このビューは、また、サイズの大きなオブジェクト ヒープに割り当てられたオブジェクト (85 KB を超えるオブジェクト) を示します。
このビューを使うと、グラフの一部をクリックするだけで、どのメソッドがどのオブジェクトを割り当てたかがわかります。 |
Histogram Relocated Types (再割り当てされた型のヒストグラム) | ガベージ コレクションで回収されずに残ったために、ガベージ コレクタによって移動されたオブジェクトを表示します。 |
Objects By Address (オブジェクトのアドレス) | 所定の時間に、マネージ ヒープ上に何があるかを表示します。 |
Histogram By Age (ヒストグラムの寿命) | マネージ ヒープ上にあるオブジェクトのライフタイムがわかります。 |
Allocation Graph (割り当てグラフ) | オブジェクトがどのようにして割り当てられたかを示す、呼び出しスタックのグラフィカル表示です。このビューは以下の目的に使用できます。
- メソッドごとの割り当てコストを理解する
- 予期していなかった割り当てを分離する
- メソッドごとの、考えられる過剰な割り当てを表示する
|
Assembly, Module, Function, and Class Graph
(アセンブリ、モジュール、関数、クラスのグラフ) | この 4 種類のビューはよく似ています。どのメソッドが、どのアセンブリ、関数、モジュールあるいはクラスで “プル” したかがわかります。 |
Heap Graph (ヒープ グラフ) | マネージ ヒープ内のすべてのオブジェクトと、その接続を示します。 |
Call Graph (呼び出しグラフ) | どのメソッドがどのメソッドを呼び出すか、またその頻度がわかります。
このグラフを使って、ライブラリ呼び出しのコストを把握したり、メソッドに対してどれだけの呼び出しが行われているか、またどのメソッドが呼び出されているかを判断できます。 |
Time Line (時系列) | アプリケーションのライフタイムに渡る、ガベージ コレクタの動作を表示します。以下の目的に使用されます。
- ガベージ コレクタの動作を調査する
- 3 つのジェネレーション (ジェネレーション 0、1、2) において、ガベージ コレクションが何回行われるか、またその頻度を割り出す
- どのオブジェクトが、ガベージ コレクションの後も回収されずに残るか、また次のジェネレーションに昇格されるかを割り出す
時点もしくは間隔を選択し、右クリックすると、その区間で割り当てられたメモリが表示されます。 |
Call Tree View (呼び出しのツリー表示) | アプリケーションの実行を示す、テキスト ベースの、時間順に配列された、階層的なビューを提供します。以下の目的に使用されます。
- どの型が割り当てられているか、またそのサイズを把握する
- メソッド呼び出しの結果、どのアセンブリがロードされているか把握する
- ファイナライザの実行回数を含む、ファイナライザの使用状況を分析する
-
Close または Dispose が実装されない、あるいは呼び出されないためにボトルネックを生じさせるメソッドを特定する
- 予期していなかった割り当てを分析する
|
アプリケーションのプロファイリング
この節では、簡単な C# コンソール アプリケーションを作成し、CLR プロファイラを使用して、このアプリケーションのプロファイリングを行います。
プロファイリング用のサンプル アプリケーションを作成する
このサンプル アプリケーション用の完全なソース コードは、この How To 情報の最後にあります。「サンプル: ProfilerSample1」および「サンプル: ProfileSample2」を参照してください。
プロファイリング用のサンプル コンソール アプリケーションを作成するには
- サンプル コードの保存用に、ProfilerSample という名前のフォルダを作成します。
- ProfilerSample フォルダ内に、ProfilerSample1.cs と ProfileSample2.cs という名前の 2つの C# ファイルを作成します。この How To 情報の最後にある「サンプル: ProfilerSample1」と「サンプル: ProfileSample2」からサンプル コードをテキスト ファイルにコピーします。
- コマンド ウィンドウを開き、ProfilerSample フォルダへ移動後、以下のコマンドを使って、コードをコンパイルします。
csc /t:exe /out:ProfilerSample1.exe ProfilerSample1.cs
csc /t:exe /out:ProfilerSample2.exe ProfilerSample2.cs
CLR プロファイラを使用して、アプリケーションのプロファイリングを行う
この手順では、ProfilerSample1.exe のプロファイリングを行います。
CLR プロファイラを使用してアプリケーションのプロファイリングを行うには
- CLR プロファイラ (CLRProfiler.exe) を起動します。
- 以下のチェック ボックスが選択されていることを確認します。
-
Profiling active (プロファイリング アクティブ)
-
Allocations (割り当て)
-
Calls (呼び出し)
- [Start Application] をクリックします。
- [Open] ウィンドウで、サンプル コードを保存した ProfilerSample フォルダ内の、ProfilerSample1.exe アプリケーションを選択します。
- 必要に応じてアプリケーションを操作して、アプリケーションを閉じます。
ASP.NET アプリケーションのプロファイリングを行う
以下の手順で、ASP.NET アプリケーションのプロファイリングを行います。
ASP.NET アプリケーションのプロファイリングを行うには
- CLR プロファイラを起動します。
- 以下のチェック ボックスが選択されていることを確認します。
-
Profiling active
-
Allocations
-
Calls
- [File] メニューの [Profile ASP.NET] をクリックします。
CLR プロファイラは、インターネット インフォメーション サービス (IIS) をシャットダウンし、プロファイリングに必要な環境変数を加えてから IIS をリスタートします。CLR プロファイラの指示にしたがって、ASP.NET アプリケーションをロードし、ASP.NET ワーカー プロセスがスタートするまで待ってください。
- Microsoft Internet Explorer を使用して、プロファイリング対象の ASP.NET アプリケーションを実行します。
Microsoft Application Center Test (ACT) などのクライアント ツールを使って、Web アプリケーションを実行することもできます。
- アプリケーションの実行が終了したら、CLR プロファイラのメイン ウィンドウの [Kill ASP.NET] をクリックします。
CLR プロファイラは IIS をシャットダウンし、環境変数を除去し、IIS をリスタートします。
メモ: 現行バージョンのツールが、ステップ 4 のページ負荷に対応していない場合があります。このような問題が生じた場合は、ASP.NET プロセスのアイデンティティについての、Machine.config ファイルの <ProcessModel> 要素を SYSTEM に変更してみてください。アプリケーションのプロファイリングが終了したら、必ず アプリケーションのアイデンティティを machine に戻してください。
ガベージ コレクションの代表的な問題を特定する
CLR Profiler.exe を使って、ガベージ コレクションに関する問題点を特定・分離することができます。このような問題には、以下のメモリ消費問題があります。
また、以下のガベージ コレクションに関する問題も含まれます。
- 過剰な回収
- 所要時間の長いオブジェクト
- ガベージ コレクションに費やした時間のパーセンテージ
メモ: CLR プロファイラを使用して行う、代表的なガベージ コレクション関連問題の解決についての詳細情報は、CLR プロファイラのインストール フォルダ内にある CLRProfiler.doc の “Common Garbage Collection Problems and How They are Reflected In These Views” を参照してください。
アプリケーションのメモリ割り当て場所を特定する
メモリ消費に関する問題に対処している間は、アプリケーションのメモリ割り当て場所を把握していることが非常に重要です。
アプリケーションのメモリ割り当て場所を特定するには、以下の手順を使います。
- CLR プロファイラをサンプル アプリケーション上で実行する。
- 割り当てたメモリの型を分析する。
- どこからメモリが割り当てられているかを割り出す。
- 割り当てを減らす方法を見極める。
ステップ 1. CLR プロファイラをサンプル アプリケーション上で実行する
CLR プロファイラを起動して、前の手順で作成した ProfilerSample1.exe アプリケーションを実行します。
ステップ 2. 割り当てたメモリの型を分析する
[View] メニューの [Histogram Allocated Types] をクリックします。CLR プロファイラは、図 1 のようなウィンドウを表示します。
図 1: CLR プロファイラの Histogram Allocated Types (割り当てられた型のヒストグラム) ビュー
このグラフは、アプリケーションのライフタイム中に割り当てられたオブジェクトを表示します。この例では、約 2 ギガバイト (GB) のオブジェクトが割り当てられ、そのほとんどすべてが文字列となっています。なぜかというと、サンプル コードが実施する方法で文字列連結を実行すると、Microsoft .NET Framework がより長い新規の文字列を割り当て、その文字列に古い拡張コンポーネントをコピーするからです。
Histogram Allocated Types (割り当てられた型のヒストグラム) ビューを使用して、サイズの大きなオブジェクトのヒープ (85 KB を超えるサイズのオブジェクト) 内に割り当てられたオブジェクトがあるかどうかを監視します。左側もしくは右側ペインにある特定の棒グラフを選択し、右クリックすると、メモリがどこから割り当てられたかがわかります。このビューは、アプリケーションのライフタイム中に割り当てられたオブジェクトについての高レベルのビューとなっています。
ステップ 3. どこからメモリが割り当てられているかを割り出す
[View] メニューの[Allocation Graph] をクリックします。または、図 1 のグラフの文字列部分のどれかを選択して、右クリックしてから [Show Who Allocated] をクリックしても良いです。このメニューをクリックすると、すべての割り当てではなく、選択した割り当ての特定の詳細情報が表示されます。CLR プロファイラは、図 2 で示すグラフを表示します。
図 2: CLR プロファイラの Allocation Graph (割り当てグラフ)
この例では、ほぼすべてのメモリが String.Concat メソッドから割り当てられていることがわかります。
Allocation Graph (割り当てグラフ) ビューでは、以下のことが可能です。
- メソッドごとの割り当てコストを把握する。
- 予想していなかった割り当てを分析する。
- 関数ごとに考えられる過剰割り当てを表示する。
- 同一の作業を実行する異なるメソッドを比較する。
ステップ 4. 割り当てを減らす方法を見極める
ここまでで、アプリケーションがどこにメモリを割り当てたかが理解できたので、メモリ消費量を削減するのに実行できる方法を検討します。この例では、文字列連結ではなく、StringBuilder を使用してみるのも選択肢のひとつです。
アプリケーションの割り当てプロファイルを分析する
アプリケーションの割り当てプロファイルでは、オブジェクトの割り当て場所、オブジェクトのライフタイム、ガベージ コレクションの動作が把握できます。以下の例では、アプリケーションがオブジェクト (およびメモリ) を必要以上に長い期間保持します。この方法では、前の手順で作成した ProfilerSample2.exe サンプル アプリケーションを使用します。
以下の手順でアプリケーションの割り当てプロファイルを分析します。
- サンプル アプリケーション上で CLR プロファイラを実行する。
- ライフタイムの長いオブジェクトを特定する。
- アプリケーションのライフタイムに渡って GC の動作を分析する。
- オブジェクトのライフタイムを短縮できるかどうか、またその方法を見極める。
ステップ 1. サンプル アプリケーション上で CLR プロファイラを実行する
CLR プロファイラを起動し、ProfilerSample2.exe アプリケーションを実行します。
ステップ 2. ライフタイムの長いオブジェクトを特定する
サンプル コードは、100,000 個の SolidBrush オブジェクトといくつかの文字列を割り当てるので、結果として割り当ての合計量は約 9 MB となります。この割り当ての大部分を、SolidBrush オブジェクトが占めています。Histogram Reallocated Types (再割り当てされた型のヒストグラム) ビューを選択すれば、約 4 MB のメモリが SolidBrush オブジェクトに再割り当てされていることがわかります。このデータが示すのは、SolidBrush オブジェクトが、ガベージ コレクションで回収されずに残り、上位のジェネレーションに昇格させられているということです。
昇格させられているオブジェクトの型と、このようなオブジェクトが使用するメモリの量を割り出すには、[View] メニューの[Objects by Address] をクリックしてください (図 3 を参照)。
図 3: CLR プロファイラの Objects by Address (アドレスごとのオブジェクト) ビュー
ジェネレーション 1 と 2 は、ほぼ SolidBrush オブジェクトで構成されています。
ステップ 3. アプリケーションのライフタイムに渡って GC の動作を分析する
より詳細なデータを確認するには、[View] メニューの [Time Line] をクリックします。[Vertical Scale (縦軸の目盛り)] を 5 に、[Horizontal Scale (横軸の目盛り)] を 1 に設定し、画面を拡大し、右にスクロールします。図 4 で示すようなウィンドウが表示されます。
図 4: CLR プロファイラの Time Line (時系列) ビュー
このチャートでは、「二重の鋸歯」パターンが出ています。ジェネレーション 0 のガベージ コレクションでは、文字列は除去されますが、ブラシは保持されます (つまり、ブラシは回収されずに残ります)。しばらくすると、ジェネレーション 1 のガベージ コレクションは、このようなブラシをクリーンアップします。二重の鋸歯パターンが意味するのは、ジェネレーション 0 ではメモリすべてを回収することができず、オブジェクトが昇格させられ、後に上位のジェネレーションでのガベージ コレクションが余儀なくされるということです。
この時点で、ガベージ コレクションでオブジェクトが回収されずに残っている場合は、調査が必要です。最初にチェックすべきアイテムが、SolidBrush ファイナライザです。
ツールのメイン メニューの [Call Tree] をクリックし、呼び出しツリーを開きます。呼び出されたファイナライザの一覧を確認するには、ファイナライザ スレッドが見つかるまで、スレッド タブをクリックしてください。
この呼び出しツリーは、ネイティブ関数 (引数不明) が、計 1,000,234 回の呼び出しをトリガしたことを示しています。ファイナライザ スレッドが実行されるまでは、オブジェクトがクリーンアップされないため、オブジェクトは回収されず、結果として上位に昇格されます。図 5 に、呼び出しツリー ビューのサンプル ウィンドウを示します。
図 5: CLR プロファイラの Call Tree (呼び出しツリー) ビュー
ステップ 4. オブジェクトのライフタイムを短縮できるかどうか、またその方法を見極める
どのオブジェクトのライフタイムが長いかがわかれば、そのライフタイムの短縮が可能かどうかを確認します。この場合、SolidBrush が必要とされなくなったら、すぐに、using ブロックを使用してラップすることにより破棄するようにします。
サンプル: ProfilerSample1
ProfilerSample1 は文字列を連結します。ProfilerSample1 用のサンプル コードを以下に示します。
ProfilerSample1.cs
using System;
public class ProfilerSample1
{
static void Main (string[] args)
{
int start = Environment.TickCount;
for (int i = 0; i < 1000; i++)
{
string s = "";
for (int j = 0; j < 100; j++)
{
s += "Outer index = ";
s += i;
s += " Inner index = ";
s += j;
s += " ";
}
}
Console.WriteLine("Program ran for {0} seconds",
0.001*(Environment.TickCount - start));
}
}
サンプルをコンパイルする
以下のコマンド ラインを使用して、コードをコンパイルします。
csc.exe /t:exe ProfilerSample1.cs
サンプル: ProfilerSample2
ProfilerSample2 は、簡単なアプリケーションで、100,000 個の SolidBrush オブジェクトと、いくつかの文字列を割り当てます。その結果、全体で約 9 MB が割り当てられます。ProfilerSample2 用サンプル コードを以下に示します。
ProfilerSample2.cs
using System;
using System.Drawing;
public class ProfilerSample2
{
static void Main()
{
int start = Environment.TickCount;
for (int i = 0; i < 100*1000; i++)
{
Brush b = new SolidBrush(Color.Black); // ブラシにはファイナライザがある
string s = new string(' ', i % 37);
// ブラシと文字列に対して何らかの作業を実施
// たとえば、このブラシで文字列を描画する - 以下省略...
}
Console.WriteLine("Program ran for {0} seconds",
0.001*(Environment.TickCount - start));
}
}
サンプルをコンパイルする
以下のコマンド ラインを使用してコードをコンパイルします。
csc.exe /t:exe ProfilerSample2.cs
補足資料
詳細情報については、以下の補足資料を参照してください。
CLR プロファイラについての詳細情報は、以下の資料を参照してください。
- CLR プロファイラを使用して、ガベージ コレクションに関する一般的な問題を解決する手法についての詳細情報は、CLRProfiler.exe ツールのインストール フォルダに含まれる CLRProfiler.doc の“Common Garbage Collection Problems and How They are Reflected In These Views” を参照してください。
- マネージ コードの重要なパフォーマンス関連要素について学習する場合は、MSDN ライブラリの “Writing High-Performance Managed Applications: A Primer” を参照してください。
- ガベージ コレクタの機能とガベージ コレクションの最適化方法についての情報は、MSDN ライブラリの 「ガベージ コレクタの基本とパフォーマンスのヒント」 を参照してください。
- CLR プロファイラを使用して、ソリューションのコードを記述する 2 通りの方法について、パフォーマンス上の差異を比較対照する手法についての情報は、MSDN TV コンテンツの “Profiling Managed Code with the CLR Profiler” を参照してください。