dotMemory 2026.1 Help

メモリトラフィックを最適化する

サンプルアプリケーション

このチュートリアルでは、dotMemory を使用してアプリケーションのメモリ使用を最適化する方法を説明します。

「メモリ使用量の最適化」とは何を意味するのでしょうか? オペレーティングシステム内の他のプロセスと同様に、GC(ガベージコレクタ)はシステムリソースを消費します。 仕組みは簡単です。GC がコレクションを多く実行するほど、CPU オーバーヘッドが大きくなり、アプリケーションのパフォーマンスが低下します。 これは通常、アプリケーションが限られた期間だけ必要な大量のオブジェクトを割り当てる場合に発生します。

このような問題を特定し分析するには、いわゆる メモリトラフィックを調べる必要があります。 トラフィック情報は、特定の時間間隔中に割り当てられ、解放されたオブジェクト(およびメモリ)の数を示します。 アプリケーションで過剰な割り当てを特定し、dotMemory を使用して取り除く方法を見てみましょう。

サンプルアプリケーション

従来、このチュートリアルで使用するサンプルアプリケーションは、コンウェイのライフゲームです。 始める前に、github(英語) からアプリケーションをダウンロードしてください。

T3Gol アプリ

このアプリケーションは多数のオブジェクト(セル)で動作するため、これらのオブジェクトがどのように割り当てられ、収集されるかのダイナミクスを調べることは興味深いでしょう。

ステップ 1. dotMemory を実行

  1. Visual Studio で Game of Life ソリューションを開きます。

  2. メニュー ReSharper | プロファイル | スタートアッププロジェクトのメモリプロファイリングを実行しています ... を使用して dotMemory を実行します。

    T2 リシャーパーメニュー UpdD M
  3. 開いた プロファイラ設定 ウィンドウで、 開始からメモリ割り当てとトラフィックデータを収集する を選択します。 これにより、dotMemory はアプリ起動後にプロファイリング情報の収集を開始します。 これは、オプションを指定した後のウィンドウの外観です。

    チュートリアル 1 ドットメモリの実行 3
  4. プロファイリングセッションを開始するには、 実行 をクリックします。 これによりアプリケーションが起動し、dotMemory のメインの 分析 #1 ページが開きます:

    チュートリアル 1 セッション 1
  5. タイムラインを表示するには、dotMemory のメインウィンドウに切り替えます。 タイムラインには、アプリケーションのメモリ使用量がリアルタイムで表示されます。 具体的には、アンマネージメモリ *、Gen0、Gen1、Gen2 ヒープおよびラージオブジェクトヒープの現在のサイズの詳細を提供します。 生命のゲームが始まるまでは、メモリの消費は止まっています。

    T3 タイムライン

ステップ 2. スナップショットを取得する

アプリケーションの起動後、メモリスナップショットの取得を開始できます。 アプリケーションの動作のダイナミックスを調べたいため、少なくとも 2 つのスナップショットを取る必要があります。 スナップショットを取得するまでの時間間隔は、さらにメモリトラフィック分析の対象となります。

当然のことながら、分配の大部分が発生した場合、Game of Life の操作の間、両方のスナップショットを取らなければなりません。 Game of Life の第 30 世代と第 100 世代の第 2 世代のスナップショットを撮りましょう。

  1. アプリケーションの 開始 ボタンを使用してゲームを開始します。

  2. 世代カウンタ(アプリの右上)が 30* に達すると、dotMemory の スナップショットを取得 ボタンをクリックします。

    T3 スナップショットを取得 1

    タイムラインを見ると、アプリケーションがリアルタイムでメモリをどのように消費するかが分かります。 アプリケーションが新しいオブジェクトを割り当てると、メモリ消費量が増加します(Gen0 ダイアグラムが大きくなります)。 ガベージコレクションが行われると、メモリ消費量が減少します。 その結果、タイムラインは鋸のようなパターンに従います。

  3. 世代カウンタが 100 に達すると、もう一度 dotMemory の スナップショットを取得 ボタンを使用して 1 つのスナップショットを取得します。

  4. Game of Life アプリを終了してプロファイリングセッションを終了します。 メインページには 2 つのスナップショットが含まれています。

    T3 スナップショット 2 を取得

ステップ 3. メモリトラフィックを分析する

ここでは、スナップショットを取得するまでの時間間隔でのメモリトラフィックを見ていきます。

  1. 両方のスナップショットが比較領域に追加されていることを確認します(比較に追加 が両方とも選択されています)。

    T3 スナップショットの比較
  2. 比較領域で メモリトラフィックを表示する をクリックすると、 メモリトラフィック ビューが開きます。 ビューには、スナップショット #1 とスナップショット #2 の間に作成された特定のタイプのオブジェクトの数が表示されます。

    T3 トラフィックビュー
  3. リストを参照してください。 GameOfLife.Cell* objects の割り当てのために、メモリトラフィック全体の約 50% である 27 MB 以上が発生します。 同時に、これらのセルの大部分、26+ MB も同様に収集された。 Game of Life の全期間にわたってセルが存在しなければならないため、非常に奇妙です。 これらのコレクションがアプリケーションのパフォーマンスを傷つけていることは間違いありません。 これらの Cell オブジェクトがどこから来たのか調べてみましょう。

  4. GameOfLife.Cell クラスの行をクリックします。 この画面の下部にあるリストは、オブジェクトを作成した機能(バックトレース)を示しています。 どうやら、これは Grid クラスの CalculateNextGeneration() メソッドです。 コード内で見つけよう。

    T3 トラフィックビュー機能
  5. Visual Studio で GameOfLife ソリューションを開きます。

  6. Grid クラスの実装を含む Grid.cs ファイルを開きます。

    T3 ソリューションエクスプローラー
  7. CalculateNextGeneration(int row, int column) メソッドを探します。

    public Cell CalculateNextGeneration(int row, int column) { bool alive; int count, age; alive = _cells[row, column].IsAlive; age = _cells[row, column].Age; count = CountNeighbors(row, column); if (alive && count < 2) return new Cell(row, column, 0, false); if (alive && (count == 2 || count == 3)) { _cells[row, column].Age++; return new Cell(row, column, _cells[row, column].Age, true); } if (alive && count > 3) return new Cell(row, column, 0, false); if (!alive && count == 3) return new Cell(row, column, 0, true); return new Cell(row, column, 0, false); }

    このメソッドは、ライフゲームの各次世代ごとに Cell オブジェクトを計算して返すようです。 しかし、これだけでは大量のメモリトラフィックの説明にはなりません。 dotMemory に戻り、どの関数が CalculateNextGeneration メソッドを呼び出しているか確認しましょう。

  8. dotMemory では、 CalculateNextGeneration メソッドを展開して、スタック内の次の関数を表示します。 Grid クラスの Update メソッドです。

    T3 トラフィックビュー機能 2
  9. コード内でこのメソッドを見つける:

    public void Update() { for (int i = 0; i < SizeX; i++) { for (int j = 0; j < SizeY; j++) { _nextGenerationCells[i, j] = CalculateNextGeneration(i,j); } } UpdateToNextGeneration(); }

    これで、膨大なメモリトラフィックの原因が明らかになりました。 これは、ライフゲームの次世代のセルを管理する nextGenerationCells 型の Cell 配列です。 各世代の更新ごとに、この配列内のセルが新しいものと置き換えられます。 前の世代に残されたセルは不要となり、しばらく後にGCによって回収されます。 アプリケーションの全期間にわたってこの配列が存在しているため、毎回新しいセルで _nextGenerationCells 配列を埋める必要はありません。 大量のメモリトラフィックをなくすには、新たなセルを作成する代わりに、既存のセルのプロパティを新しい値で更新する必要があります。 これをコードで実装しましょう。

  10. 実際、例題用アプリケーションにはすでに CalculateNextGeneration メソッドの必要な実装が含まれています。 このメソッドは、参照渡しされたセルの IsAlive フィールドと Age フィールドを更新します:

    public void CalculateNextGeneration(int row, int column, ref bool isAlive, ref int age) { ... }

    この問題を修正するには、このメソッドを使って _nextGenerationCells 配列を更新している Update() 内の行のコメント解除を行ってください。 最後に、 Update() メソッドは次のようになります:

    public void Update() { bool alive = false; int age = 0; for (int i = 0; i < SizeX; i++) { for (int j = 0; j < SizeY; j++) { CalculateNextGeneration(i, j, ref alive, ref age); _nextGenerationCells[i, j].IsAlive = alive; _nextGenerationCells[i, j].Age = age; } } UpdateToNextGeneration(); }

    では、これらの変更を適用して、メモリトラフィックへどのような影響があるか確認しましょう。

  11. アプリケーションをもう一度ビルドします。 Step 1 の手順を繰り返してください。 dotMemory を実行し、 Step 2 も実行してください。 スナップショットを取得して新しいスナップショットを用意しましょう。

  12. メモリトラフィック ビューを開き、収集したスナップショット間のメモリトラフィックを確認します(Step 3 のサブステップ 1 および 2 で説明されています: メモリトラフィックを分析

    T3 トラフィックビューが修正されました

    GameOfLife.Cell クラスはもうリストにはありません ! その結果、全体のトラフィックが 40% 減少し(33MB まで)、これは非常に優れた最適化です。

2026 年 6 月 12 日