Share via


ガベージ コレクションのパフォーマンスの向上

C# と Visual Basic で記述されたユニバーサル Windows プラットフォーム (UWP) アプリは、.NET ガベージ コレクターによって、自動的にメモリ管理が行われます。 このセクションでは、UWP アプリでの .NET ガベージ コレクターの動作とパフォーマンスに関するベスト プラクティスについて説明します。 .NET ガベージ コレクターのしくみと、ガベージ コレクターのパフォーマンスをデバッグおよび分析するためのツールについて詳しくは、「ガベージ コレクション」をご覧ください。

ガベージ コレクターの既定の動作に介入が必要な場合、アプリに関して一般的なメモリの問題があることを強く示唆しています。 詳しくは、「Memory Usage Tool while debugging in Visual Studio 2015 (Visual Studio でのユーザー モード デバッグの設定)」をご覧ください。 このトピックは、C# と Visual Basic にのみ適用されます。

 

ガベージ コレクターは、ガベージ コレクションで実行する必要がある作業の量とマネージ ヒープのメモリ消費量とのバランスを考慮して、実行するタイミングを決めます。 ガベージ コレクターがこの処理を行う方法の 1 つとして、ヒープをジェネレーション別に分け、ほとんどの場合はヒープの一部のみのコレクションを実行します。 マネージ ヒープには、次の 3 つのジェネレーションがあります。

  • ジェネレーション 0。 このジェネレーションには、新しく割り当てられた、85 KB 未満のオブジェクトが含まれます。85 KB 以上のオブジェクトは、大きなオブジェクト ヒープに属します。 大きなオブジェクト ヒープのコレクションは、ジェネレーション 2 のコレクション時に行われます。 ジェネレーション 0 のコレクションは最も頻繁に行われ、ローカル変数などの有効期間が短いオブジェクトをクリーンアップします。
  • 第 1 世代。 このジェネレーションには、ジェネレーション 0 のコレクションの後に残ったオブジェクトが含まれます。 このジェネレーションは、ジェネレーション 0 とジェネレーション 2 の間のバッファーとして機能します。 ジェネレーション 1 のコレクションは、ジェネレーション 0 のコレクションよりも発生頻度が低く、前のジェネレーション 0 のコレクション時にアクティブだった一時オブジェクトをクリーンアップします。 ジェネレーション 1 のコレクション時には、ジェネレーション 0 のコレクションも実行されます。
  • ジェネレーション 2。 このジェネレーションには、ジェネレーション 0 とジェネレーション 1 のコレクションの後に残った、有効期間が長いオブジェクトが含まれます。 ジェネレーション 2 のコレクションは最も頻度が低く、85 KB 以上のオブジェクトを含む大きなオブジェクト ヒープなど、マネージ ヒープ全体のコレクションを実行します。

ガベージ コレクターのパフォーマンスは、ガベージ コレクションの実行にかかる時間とマネージ ヒープのメモリ消費量という 2 つの側面から測定できます。 ヒープ サイズが 100 MB 未満の小規模なアプリの場合は、メモリ消費量の削減に重点を置きます。 アプリのマネージ ヒープが 100 MB を超える場合は、ガベージ コレクションの実行時間の短縮にのみ重点を置きます。 以下に .NET ガベージ コレクターのパフォーマンスを向上させる方法を紹介します。

メモリ消費量の削減

関連情報

アプリ内でオブジェクトを参照している場合は、そのオブジェクトと、そのオブジェクトが参照するすべてのオブジェクトのコレクションが行われません。 .NET コンパイラは、変数が使われなくなったときにうまく削除を行うため、その変数によって保持されているオブジェクトがコレクションの対象になります。 しかし、アプリで使っているライブラリによってオブジェクト グラフの一部が所有されている場合があるため、あるオブジェクトが別のオブジェクトを参照しているかどうかがわからないことがあります。 ガベージ コレクションの後に残るオブジェクトを調べるツールと方法については、「ガベージ コレクションとパフォーマンス」をご覧ください。

必要に応じたガベージ コレクションの実行

アプリのパフォーマンスを測定し、コレクションを実行することでパフォーマンスが向上すると判断した場合にのみ、ガベージ コレクションを実行します。

GC.Collect(n) を呼び出すと、ジェネレーション別にガベージ コレクションを実行できます。n には、コレクションを実行するジェネレーション (0、1、または 2) を指定します。

アプリでは、ガベージ コレクションを強制的に実行しないでください。ガーベジ コレクターはコレクションの実行に最も適したタイミングを判断するためにさまざまなヒューリスティックを使うため、コレクションを強制的に実行すると、多くの場合は CPU が不必要に使われます。 ただし、アプリ内のたくさんのオブジェクトが使われなくなることがわかっており、そのメモリをシステムに返す必要がある場合は、ガベージ コレクションを強制的に実行してもかまいません。 たとえば、ゲームの読み込みシーケンスの最後にコレクションを実行すると、ゲームプレイが始まる前にメモリを解放できます。   ガベージ コレクションが誤って何度も実行されないようにするには、GCCollectionModeOptimized に設定します。 これにより、コレクションが妥当で生産的であると判断した場合にのみ、ガベージ コレクターがコレクションを開始するようになります。

ガベージ コレクションの実行時間の短縮

このセクションは、アプリを分析済みで、長いガベージ コレクションの実行時間を確認した場合を対象としています。 ガベージ コレクションに関連した停止時間には、次のような時間があります: 1 回のガベージ コレクションの実行にかかる時間。アプリでのガベージ コレクションの合計実行時間。 コレクションの実行にかかる時間は、コレクターが分析する必要があるライブ データの量によって決まります。 ジェネレーション 0 とジェネレーション 1 はサイズで制限されていますが、ジェネレーション 2 は、アプリ内の有効期間が長いアクティブなオブジェクトが増えると、増え続けます。 そのため、ジェネレーション 0 とジェネレーション 1 のコレクションの実行時間には限度がありますが、ジェネレーション 2 のコレクションの実行時間は長くなる可能性があります。 ガベージ コレクションでは、メモリの割り当て要求を満たすためにメモリを解放します。そのため、ガベージ コレクションの実行頻度は、通常、割り当てるメモリの量によって決まります。

ガベージ コレクターは処理を実行するためにアプリを一時停止することがありますが、コレクションを実行している間ずっとアプリを停止するわけではありません。 特にジェネレーション 0 とジェネレーション 1 のコレクションの場合は、通常、アプリが停止したことにユーザーは気が付きません。 .NET ガベージ コレクターのバックグラウンド ガベージ コレクション機能を使うと、アプリを実行しているときに、ジェネレーション 2 のコレクションを同時に実行でき、アプリの停止時間が短くなります。 ただし、常にジェネレーション 2 のコレクションをバックグラウンド コレクションとして実行できるわけではありません。 その場合は、ヒープがかなり大きい (100 MB を超えている) と、停止したことにユーザーが気付く可能性があります。

ガベージ コレクションが頻繁に行われると、アプリでの CPU (と電力) 消費量の増加、読み込み時間の増加、またはフレーム レートの低下につながる可能性があります。 次に、ガベージ コレクションの実行時間を短縮し、マネージ UWP アプリでのコレクションに関連した一時停止を減らすための手法をいくつか紹介します。

割り当てるメモリの削減

オブジェクトを割り当てていない場合は、システムでメモリ不足にならない限り、ガベージ コレクターは実行されません。 割り当てるメモリの量を減らすと、ガベージ コレクションの頻度を直接的に減らすことができます。

アプリのどこかのセクションで一時停止するのが好ましくない場合は、パフォーマンスがあまり重視されないときに前もって必要なオブジェクトを割り当てることができます。 たとえば、ゲームでは、レベルの読み込み画面を表示している間にゲームプレイに必要なすべてのオブジェクトを割り当て、ゲームプレイ中は割り当てを行わないようにします。 こうすることで、ユーザーがゲームをプレイしているときに一時停止することがなくなり、その結果、フレーム レートの速度と一貫性を向上させることができます。

有効期間が中程度のオブジェクトの回避によるジェネレーション 2 のコレクションの削減

ジェネレーション別のガベージ コレクションは、アプリ内のオブジェクトの有効期間が実際に短いか長い場合に最も効果を発揮します。 有効期間が短いオブジェクトのコレクションは負荷の少ないジェネレーション 0 とジェネレーション 1 で実行され、有効期間が長いオブジェクトは頻繁にコレクションが実行されないジェネレーション 2 に昇格します。 有効期間が長いオブジェクトとは、アプリの有効期間全体または特定のページやゲーム レベルなどで長期間使われるオブジェクトのことです。

有効期間が一時的であっても長いオブジェクトは、ジェネレーション 2 に昇格します。そのため、そうしたオブジェクトを頻繁に作成すると、負荷の高いジェネレーション 2 のコレクションが多く発生します。 ジェネレーション 2 のコレクションを減らすには、既に存在するオブジェクトをリサイクルするか、オブジェクトをもっと早く解放します。

有効期間が中程度のオブジェクトの一般的な例としては、ユーザーがスクロールする一覧に項目を表示するために使われるオブジェクトがあります。 一覧の項目がスクロールによって表示されるときにオブジェクトが作成され、一覧の項目がスクロールによって表示されなくなったときにオブジェクトが参照されなくなる場合は、通常、アプリでジェネレーション 2 のコレクションが大量に発生します。 このような場合は、ユーザーにアクティブに表示されるデータに対して一連のオブジェクトを事前に割り当て、それらを再利用し、一覧の項目が表示されるときに有効期間が短いオブジェクトを使って情報を読み込むことができます。

有効期間が短く、サイズが大きいオブジェクトの回避によるジェネレーション 2 のコレクションの削減

85 KB 以上のオブジェクトは大きなオブジェクト ヒープ (LOH) に割り当てられ、ジェネレーション 2 の一部としてコレクションが実行されます。 バッファーなどの一時変数が 85 KB を超えている場合は、ジェネレーション 2 のコレクションでクリーンアップされます。 一時変数を 85 KB 未満に制限すると、アプリでのジェネレーション 2 のコレクションの回数が減少します。 一般的な方法としては、バッファー プールを作成し、プールのオブジェクトを再利用して、サイズが大きい一時的な割り当てを回避します。

参照の多いオブジェクトの回避

ガベージ コレクターは、オブジェクト間の参照の追跡をアプリのルートから開始して、有効なオブジェクトを特定します。 詳しくは、「ガベージ コレクションの実行時の動作」をご覧ください。 オブジェクトに参照がたくさん含まれていると、ガベージジ コレクターの処理が増えます。 一般的な手法としては (特に大きなオブジェクトの場合)、参照の多いオブジェクトを参照のないオブジェクトに変換します (たとえば、参照を保存する代わりに、インデックスを保存します)。 当然、この手法を使うことができるのは、論理的に可能な場合に限られます。

オブジェクト参照をインデックスに置き換えると、アプリに複雑な変更が加わり、混乱を招く可能性があります。また、この手法は、大きなオブジェクトに多数の参照が含まれている場合が最も効果的です。 アプリでのガベージ コレクションの実行時間の長さが、参照の多いオブジェクトに関連していることがわかっている場合にのみ行ってください。