一般的なアドバイスとして、コードからGC.Collect
を呼び出すべきではありませんが、この規則の例外は何ですか?
ガベージコレクションを強制するのが理にかなっている可能性がある、非常に具体的ないくつかのケースしか考えられません。
頭に浮かぶ1つの例は、間隔をあけて起動し、何らかのタスクを実行してから、長時間スリープするサービスです。この場合、すぐにアイドルになるプロセスが必要以上のメモリを保持しないように、収集を強制することをお勧めします。
GC.Collect
を呼び出すことが許容される他のケースはありますか?
重要なオブジェクトのセット(特に世代1および2にあると思われるオブジェクト)がガベージコレクションの対象となると信じる十分な理由がある場合は、パフォーマンスヒットが小さいという点で収集するのに適切な時期になります。 。
これの良い例は、大きなフォームを閉じたばかりの場合です。すべてのUIコントロールをガベージコレクションできるようになりました。フォームが閉じられたときの非常に短い休止は、おそらくユーザーには気付かないでしょう。
更新2.7.2018
.NET 4.5以降-GCLatencyMode.LowLatency
とGCLatencyMode.SustainedLowLatency
があります。これらのモードのいずれかを開始および終了する場合、GC.Collect(2, GCCollectionMode.Forced)
を使用して強制的にフルGCを実行することをお勧めします。
.NET 4.6以降-GC.TryStartNoGCRegion
メソッドがあります(読み取り専用値GCLatencyMode.NoGCRegion
の設定に使用)。これ自体で、十分なメモリを解放するために完全なブロッキングガベージコレクションを実行できますが、一定期間GCを許可しない場合は、前後に完全なGCを実行することもお勧めします。
出典:MicrosoftエンジニアBen Watson's:Writing High-Performance .NET Code、2nd Ed。 2018年.
見る:
GC.Collect
を使用するのは、粗雑なパフォーマンス/プロファイラーテストリグを作成するときだけです。つまり、テストするコードのブロックが2つ(またはそれ以上)あります-次のようなものです:
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...
TestA()
とTestB()
が可能な限り同様の状態で実行されるようにします。つまり、TestB()
は、TestA
が転倒点の非常に近くにあるという理由だけで打撃を受けません。
古典的な例は、単純なコンソールexe(Main
メソッドなど)で、ループ文字列の連結とStringBuilder
の違いを示します。
正確なものが必要な場合、これは2つの完全に独立したテストになりますが、テスト中にGCを最小化(または正規化)して動作の大まかな感触を得るには、多くの場合これで十分です。
量産コード中?まだ使用していません;-p
ベストプラクティスは、ほとんどの場合、ガベージコレクションを強制しないことです。解決すると、ガベージコレクションを強制する必要がなくなり、システムが大幅に高速化されます。)
いくつかのケースがありますyouガベージコレクターが行います。これは、マルチユーザーアプリケーション、または一度に複数の要求に応答するサービスでは当てはまりません。
ただし、一部のバッチタイプ処理では、GCの詳細がわかります。例えば。そのアプリケーションを検討してください。
may各ファイルを処理した後、完全なガベージコレクションを強制する必要があることを(慎重に)テストすることができます。
別のケースは、数分ごとに起動していくつかのアイテムを処理するサービスであり、スリープ中は状態を保持しません。次に、スリープに入る直前に完全なコレクションを強制しますmay価値があります。
コレクションを強制することを検討するのは、最近多くのオブジェクトが作成され、現在参照されているオブジェクトが非常に少ないことを知っているときだけです。
GCを自分で強制することなく、このタイプのことについてのヒントを与えることができる場合は、ガベージコレクションAPIが必要です。
「 Rico Mariani's Performance Tidbits 」も参照してください
1つのケースは、 WeakReference を使用するコードを単体テストしようとしている場合です。
ガベージコレクターが知らないアプリの性質について何か知っているときに、GC.Collect()を呼び出すことができます。著者として、これは非常に可能性が高いと考えるのは魅力的です。ただし、真実はGCが十分に作成され、テストされたエキスパートシステムに相当することであり、GCが知らない低レベルのコードパスについて何かを知ることはまれです。
あなたがいくつかの追加情報を持っているかもしれない場所について考えることができる最良の例は、アイドル期間と非常に忙しい期間の間を循環するアプリです。繁忙期に可能な限り最高のパフォーマンスを求めているため、アイドル時間を使用してクリーンアップを行います。
ただし、ほとんどの場合、GCはこれを行うのに十分なほどスマートです。
大規模な24/7または24/6システム(メッセージ、RPC要求に反応するシステム、またはデータベースやプロセスを継続的にポーリングするシステム)では、メモリリークを識別する方法があると便利です。このため、処理を一時的に中断し、完全なガベージコレクションを実行するメカニズムをアプリケーションに追加する傾向があります。これにより、システムは静止状態になり、残りのメモリは正当に長く存続するメモリ(キャッシュ、構成など)であるか、または「リーク」されます(実際にはルート化されることが期待されない、または望まれないオブジェクト)。
このメカニズムを使用すると、アクティブな処理からのノイズでレポートが曇らないため、メモリ使用量を簡単にプロファイルできます。
すべてのガベージを確実に取得するには、2つのコレクションを実行する必要があります。
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
最初のコレクションにより、ファイナライザを持つオブジェクトがファイナライズされます(ただし、これらのオブジェクトは実際にはガベージコレクションされません)。 2番目のGCは、これらのファイナライズされたオブジェクトをガベージコレクションします。
メモリフラグメンテーションソリューションとして。大量のデータをメモリストリームに書き込む(ネットワークストリームから読み取る)ときに、メモリ不足の例外が発生していました。データは8Kのチャンクで書き込まれました。 128Mに達した後、使用可能なメモリがたくさんあるにもかかわらず例外がありました(ただし、断片化されていました)。 GC.Collect()を呼び出すことで問題が解決しました。修正後、1G以上を処理できました。
Rico Marianiによるこの記事をご覧ください。彼は、GC.Collectを呼び出す2つのルールを示します(ルール1は「しない」)。
私は配列とリストでいくつかのパフォーマンステストを行っていました:
private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
var lstNumbers = new List<int>();
for(var i = 1; i <= count; i++)
{
lstNumbers.Add(i);
}
return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
var lstNumbers = new int[count];
for (var i = 1; i <= count; i++)
{
lstNumbers[i-1] = i + 1;
}
return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
return Enumerable.Range(1, count).ToArray();
}
static void performance_100_Million()
{
var sw = new Stopwatch();
sw.Start();
var numbers1 = GetSomeNumbers_List_int();
sw.Stop();
//numbers1 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
var numbers2 = GetSomeNumbers_Array();
sw.Stop();
//numbers2 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
var numbers3 = GetSomeNumbers_Enumerable_Range();
sw.Stop();
//numbers3 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}
getSomeNumbers_Enumerable_RangeメソッドでOutOfMemoryException
を取得しました。唯一の回避策は、次の方法でメモリの割り当てを解除することです。
numbers = null;
GC.Collect();
GC.Collect()を呼び出す必要がほとんどある1つの例は、Interopを介してMicrosoft Officeを自動化するときです。 OfficeのCOMオブジェクトは自動的に解放されないため、Office製品のインスタンスが大量のメモリを消費する可能性があります。これが問題なのか、仕様によるのかはわかりません。インターネットに関するこのトピックに関する記事はたくさんありますので、ここではあまり詳しく説明しません。
Interopを使用してプログラミングする場合、通常はMarshal.ReleseComObject()を使用しますが、every single COMオブジェクトを手動で解放する必要があります。さらに、ガベージコレクションを手動で呼び出すと、少し「クリーンアップ」できます。 Interopオブジェクトの処理が完了したときに次のコードを呼び出すと、かなり役立つようです。
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
私の個人的な経験では、ReleaseComObjectとガベージコレクションの手動呼び出しを組み合わせて使用するとgreatlyは、Office製品、特にExcelのメモリ使用量を削減します。
あなたの例では、GC.Collectを呼び出すことは問題ではなく、設計上の問題があると思います。
間隔をあけて(時間を設定して)起動する場合は、プログラムを1回の実行用に作成(タスクを1回実行)してから終了する必要があります。次に、プログラムをスケジュールされたタスクとして設定し、スケジュールされた間隔で実行します。
このように、GC.Collectの呼び出しに気を使う必要はありません(これはまれにする必要があります)。
そうは言っても、Rico Marianiにはこのテーマに関する素晴らしいブログ投稿があります。
GC.Collect()を呼び出す便利な場所の1つは、メモリリークが発生していないことを確認する場合の単体テストです(たとえば、WeakReferencesまたはConditionalWeakTable、動的に生成されたコードなどで何かをしている場合)。
たとえば、次のようなテストがいくつかあります。
WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);
WeakReferencesを使用すること自体が問題であると主張することもできますが、そのような動作に依存するシステムを作成している場合、GC.Collect()を呼び出すことは、そのようなコードを検証する良い方法です。
私はまだこれについてかなり自信がありません。私はApplication Serverで7年間働いています。大規模なインストールでは24 GB Ramを使用します。非常にマルチスレッド化され、GC.Collect()のすべての呼び出しは、本当にひどいパフォーマンスの問題にぶつかりました。
すぐにこれを行うのが賢明だと思ったときに、多くのサードパーティのコンポーネントがGC.Collect()を使用しました。そのため、Excel-Reportsの単純な束が、1分に数回、すべてのスレッドでApp Serverをブロックしました。
GC.Collect()呼び出しを削除するために、すべてのサードパーティコンポーネントをリファクタリングする必要があり、これを実行した後はすべて正常に機能しました。
しかし、Win32でもサーバーを実行しています。ここで、OutOfMemoryExceptionを取得した後、GC.Collect()を多用し始めました。
しかし、32ビットでOOMを取得し、GC.Collect()を呼び出さずに同じ操作を再度実行しようとすると、うまくいくのでよく気づいたので、これについてもかなり不安です。
私が疑問に思うのは、OOM例外自体です... .Net Frameworkを記述していて、メモリブロックを割り当てることができない場合、GC.Collect()を使用し、メモリをデフラグします(??)、もう一度やり直します、まだ空きメモリブロックが見つからない場合は、OOM-Exceptionをスローします。
または、GC.Collectのパフォーマンスの問題の欠点により、少なくともこの動作を構成可能なオプションとして設定します。
今、私のアプリにはこの問題を「解決する」ためのこのようなコードがたくさんあります:
public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{
int oomCounter = 0;
int maxOOMRetries = 10;
do
{
try
{
return func(a1, a2);
}
catch (OutOfMemoryException)
{
oomCounter++;
if (maxOOMRetries > 10)
{
throw;
}
else
{
Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
GC.Collect();
}
}
} while (oomCounter < maxOOMRetries);
// never gets hitted.
return default(TResult);
}
(Thread.Sleep()の動作は実際にアプリ特有の動作であることに注意してください。ORMキャッシングサービスを実行しているため、RAMが事前定義された値を超えると、サービスはすべてのキャッシュオブジェクトを解放するのに時間がかかるためです値。したがって、最初は数秒待機し、OOMが発生するたびに待機時間が増加します。
using(var stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Png);
techObject.Last().Image = Image.FromStream(stream);
bitmap.Dispose();
// Without this code, I had an OutOfMemory exception.
GC.Collect();
GC.WaitForPendingFinalizers();
//
}
ごめんなさいよりも安全なほうが良い状況がいくつかあります。
1つの状況を次に示します。
IL書き換えを使用して、C#でnmanaged DLLを作成することができます(これが必要な状況があるため)。
たとえば、DLLがクラスレベルでバイトの配列を作成するとします-エクスポートされた関数の多くはそのような配列にアクセスする必要があるためです。 DLLがアンロードされるとどうなりますか?ガベージコレクターはその時点で自動的に呼び出されますか?わかりませんが、アンマネージド DLLであるため、GCが呼び出されない可能性があります。そして、呼び出されなかったら大きな問題になります。 DLLがアンロードされると、ガベージコレクターも-誰がガベージコレクションを担当し、どのようにガベージコレクションを行うのでしょうか。 C#のガベージコレクターを使用する方が適切です。クラスレベル変数がnullに設定され、ガベージコレクターが呼び出されるクリーンアップ関数(DLLクライアントで使用可能)があります。
転ばぬ先の杖。
Scott Holdenの GC.Collectを呼び出すタイミング(および呼び出さないタイミング)に関するブログエントリ は 。NET Compact Framework に固有ですが、ルールは一般的にすべてのマネージ開発に適用されます。
簡単な答えは:決して!
GC.Collect()は非常に高価なので、使用しないようにしてください。以下に例を示します。
public void ClearFrame(ulong timeStamp)
{
if (RecordSet.Count <= 0) return;
if (Limit == false)
{
var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
if (seconds <= _preFramesTime) return;
Limit = true;
do
{
RecordSet.Remove(RecordSet[0]);
} while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
}
else
{
RecordSet.Remove(RecordSet[0]);
}
GC.Collect(); // AVOID
}
テスト結果:CPU使用率12%
これに変更すると:
public void ClearFrame(ulong timeStamp)
{
if (RecordSet.Count <= 0) return;
if (Limit == false)
{
var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
if (seconds <= _preFramesTime) return;
Limit = true;
do
{
RecordSet[0].Dispose(); // Bitmap destroyed!
RecordSet.Remove(RecordSet[0]);
} while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
}
else
{
RecordSet[0].Dispose(); // Bitmap destroyed!
RecordSet.Remove(RecordSet[0]);
}
//GC.Collect();
}
テスト結果:CPU使用率2-3%
これは質問には関係ありませんが、.NETのXSLT変換(XSLCompiledTranform)の場合、選択の余地はありません。もう1つの候補はMSHTMLコントロールです。
Small object heap(SOH)とLarge object heap(LOH)があるため
GC.Collect()を呼び出して、SOPの参照解除オブジェクトをクリアし、生存しているオブジェクトを次世代に移動できます。
.net4.5では、 largeobjectheapcompactionmode を使用してLOHを圧縮することもできます
gCを呼び出す理由の1つは、Raspberry Pi(モノで実行)のようなメモリの少ない小さなARMコンピューターにあります。未割り当てのメモリフラグメントがシステムRAMを使いすぎると、Linux OSが不安定になる可能性があります。メモリオーバーフローの問題を取り除くために、毎秒GC(!)を呼び出さなければならないアプリケーションがあります。
別の良い解決策は、オブジェクトが不要になったときに破棄することです。残念ながら、これは多くの場合、それほど簡単ではありません。
4.5未満のバージョンの.netを使用している場合、手動収集は避けられない可能性があります(特に、多くの「大きなオブジェクト」を処理している場合)。
このリンクはその理由を説明しています:
https://blogs.msdn.Microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/