プロジェクトのコードのレビューを始めたところ、次のようなものが見つかりました。
GC.Collect();
GC.WaitForPendingFinalizers();
これらの線は通常、効率を上げるという根拠の下でオブジェクトを破壊すると考えられているメソッドに表示されます。私はこの発言をしました:
1、2、3は正しいですか?あなたの答えを裏付ける参考資料を教えてください。
私の発言についてはほぼ確実ですが、なぜこれが問題なのかをチームに説明するために、私の主張を明確にする必要があります。それが私が確認と参照を求めている理由です。
簡単に言えば、それを取り出してください。そのコードは、パフォーマンスをほとんど決して向上させません、または長期的なメモリ使用。
あなたの主張はすべて真実です。 (それはcanデッドロックを生成します;これは常にそれが意味するわけではありませんwill)GC.Collect()
を呼び出すと収集されますすべてのGC世代のメモリ。これは2つのことを行います。
収集不可能なオブジェクトを次世代に昇格させます。つまり、コレクションを強制しても、何らかのオブジェクトへの参照がまだある場合は常に、そのオブジェクトは次の世代に昇格されます。通常、これは比較的まれにしか発生しませんが、以下のようなコードはこれをはるかに頻繁に強制します。
_void SomeMethod()
{
object o1 = new Object();
object o2 = new Object();
o1.ToString();
GC.Collect(); // this forces o2 into Gen1, because it's still referenced
o2.ToString();
}
_
GC.Collect()
がない場合、これらのアイテムは両方とも次の機会に収集されます。 Withコレクションを書き込みとして、_o2
_はGen1で終了します-つまり、自動化されたGen0コレクションはできませんそのメモリを解放します。
さらに大きな恐怖に注意する価値もあります。デバッグモードでは、GCの機能が異なり、まだスコープ内にある変数を再利用しません(現在のメソッドで後で使用されない場合でも)。したがって、デバッグモードでは、上記のコードは_o1
_を呼び出すときに_GC.Collect
_も収集しないため、_o1
_と_o2
_の両方が昇格されます。これにより、コードをデバッグするときに、非常に不安定で予期しないメモリ使用が発生する可能性があります。 ( this などの記事は、この動作を強調しています。)
EDIT:この動作をテストしたところ、実際の皮肉なことに、次のようなメソッドがあるとします。
_void CleanUp(Thing someObject)
{
someObject.TidyUp();
someObject = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
_
...次に、RELEASEモードであっても、someObjectのメモリを明示的に解放しません。次のGC生成にプロモートします。
理解しやすいポイントがあります。GCを実行すると、実行ごとに多くのオブジェクトが自動的にクリーンアップされます(たとえば、10000)。破棄のたびに呼び出すと、実行ごとに約1つのオブジェクトがクリーンアップされます。
GCはオーバーヘッドが高い(スレッドを停止および開始する必要があるため、すべてのオブジェクトを生きたままスキャンする必要がある)ため、呼び出しをバッチ処理することをお勧めします。
また、すべてのオブジェクトの後にクリーンアップからどのgoodが出てくる可能性がありますか?これはバッチ処理よりもどのように効率的でしょうか?
ポイント番号3は技術的には正しいですが、ファイナライザの間に誰かがロックした場合にのみ発生します。
この種の呼び出しがなくても、ファイナライザ内のロックは、ここにあるものよりもさらに悪いです。
GC.Collect()
を呼び出すとパフォーマンスが向上する場合があります。
これまでのところ、私のキャリアの中で2回、おそらく3回行っています。 (または、私が行った場所を含めて結果を測定し、もう一度取り出した場合、おそらく5〜6回です。これは、実行する必要があるものです常に実行後に測定します)。
短期間に数百または数千メガのメモリを使い続け、その後、メモリの使用量をはるかに少ないメモリに長期間切り替えると、大規模な、または重大な改善になる可能性があります。明示的に収集します。ここで何が起こっているのですか?
他のどこでも、彼らはせいぜい遅くして、より多くのメモリを使用するでしょう。
私の他の答えをここで見てください:
自分でGC.Collect()を呼び出すと、2つのことが発生する可能性があります。結局、コレクションの実行にmore時間を費やすことになります(手動のGCに加えて、通常のバックグラウンドコレクションが引き続き行われるためです。 Collect())を実行すると、メモリlongerに留まります(そこに移動する必要のないものをより高次の世代に強制したため)。つまり、GC.Collect()を自分で使用することは、ほとんどの場合悪い考えです。
自分でGC.Collect()を呼び出す必要があるのは、ガベージコレクターが知るのが難しいプログラムに関する特定の情報があるときだけです。正規の例は、明確なビジーで軽い負荷サイクルを持つ長期実行プログラムです。負荷の軽い期間の終わり近くに、ビジーサイクルの前に強制的にコレクションを強制して、ビジーサイクルに対してリソースができるだけ解放されるようにすることができます。しかし、ここでも、アプリの構築方法を再考することで、より良い結果が得られる可能性があります(つまり、スケジュールされたタスクがうまく機能するでしょうか?)。
これは1回だけ使用しました。CrystalReportドキュメントのサーバー側キャッシュをクリーンアップするためです。 Crystal Reports Exceptionの私の応答を参照してください:システム管理者が構成したレポート処理ジョブの最大数に達しました
オブジェクトが適切にクリーンアップされないことがあったため、WaitForPendingFinalizersは特に役立ちました。 Webページでのレポートのパフォーマンスが比較的遅いことを考えると、GCによるわずかな遅延は無視でき、メモリ管理の改善により、サーバー全体がより快適になりました。
@Grzenioと同様の問題が発生しましたが、より大きな2次元配列(1000x1000から3000x3000のオーダー)で作業しています。これはWebサービスにあります。
より多くのメモリを追加することが常に正しい答えであるとは限りません。コードとユースケースを理解する必要があります。 GC収集を行わない場合、16〜32 GBのメモリが必要です(お客様のサイズによって異なります)。それがないと、32〜64 GBのメモリが必要になり、それでもシステムが影響を受けないという保証はありません。 .NETガベージコレクターは完璧ではありません。
私たちのウェブサービスは、500から5000万の文字列(設定によってはキーと値のペアごとに約80から140文字)のインメモリキャッシュを備えていますブール値は、作業を行うために別のサービスに渡されました。 1000x1000の「マトリックス」(2次元配列)の場合、これは〜25mbですリクエストごと。ブール値は、(キャッシュに基づいて)必要な要素を示します。各キャッシュエントリは、「マトリックス」内の1つの「セル」を表します。
サーバーのページングが原因でメモリ使用率が80%を超えると、キャッシュのパフォーマンスが劇的に低下します。
私たちが見つけたのは、明示的にGCしない限り、キャッシュのパフォーマンスが大幅に低下する90〜95%の範囲になるまで、.netガベージコレクターは一時的な変数を「クリーンアップ」しないことです。
ダウンストリームプロセスには長い時間がかかることが多いため(3〜900秒)、GCコレクションのパフォーマンスへの影響は無視できます(収集ごとに3〜10秒)。この収集を開始しましたafterすでにクライアントに応答を返していました。
最終的には、GCパラメーターを構成可能にしました。net4.6では、さらにオプションがあります。以下は、使用した.net 4.5コードです。
if (sinceLastGC.Minutes > Service.g_GCMinutes)
{
Service.g_LastGCTime = DateTime.Now;
var sw = Stopwatch.StartNew();
long memBefore = System.GC.GetTotalMemory(false);
context.Response.Flush();
context.ApplicationInstance.CompleteRequest();
System.GC.Collect( Service.g_GCGeneration, Service.g_GCForced ? System.GCCollectionMode.Forced : System.GCCollectionMode.Optimized);
System.GC.WaitForPendingFinalizers();
long memAfter = System.GC.GetTotalMemory(true);
var elapsed = sw.ElapsedMilliseconds;
Log.Info(string.Format("GC starts with {0} bytes, ends with {1} bytes, GC time {2} (ms)", memBefore, memAfter, elapsed));
}
.net 4.6で使用するために書き換えた後、ガベージコレクションを2つのステップ(単純な収集と圧縮収集)に分割しました。
public static RunGC(GCParameters param = null)
{
lock (GCLock)
{
var theParams = param ?? GCParams;
var sw = Stopwatch.StartNew();
var timestamp = DateTime.Now;
long memBefore = GC.GetTotalMemory(false);
GC.Collect(theParams.Generation, theParams.Mode, theParams.Blocking, theParams.Compacting);
GC.WaitForPendingFinalizers();
//GC.Collect(); // may need to collect dead objects created by the finalizers
var elapsed = sw.ElapsedMilliseconds;
long memAfter = GC.GetTotalMemory(true);
Log.Info($"GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");
}
}
// https://msdn.Microsoft.com/en-us/library/system.runtime.gcsettings.largeobjectheapcompactionmode.aspx
public static RunCompactingGC()
{
lock (CompactingGCLock)
{
var sw = Stopwatch.StartNew();
var timestamp = DateTime.Now;
long memBefore = GC.GetTotalMemory(false);
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
var elapsed = sw.ElapsedMilliseconds;
long memAfter = GC.GetTotalMemory(true);
Log.Info($"Compacting GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");
}
}
これを調査するために多くの時間を費やしたので、これが他の誰かの助けになることを願っています。
ここの答えに広く同意します-ネイティブのpython実装は比較的効果的で予測可能であるため、独自のガベージコレクションを実行する必要があることはほとんどありません。問題を自分の手に取り入れるのは理にかなっています: