web-dev-qa-db-ja.com

GC.Collect();を使用するのが正しいGC.WaitForPendingFinalizers();?

プロジェクトのコードのレビューを始めたところ、次のようなものが見つかりました。

GC.Collect();
GC.WaitForPendingFinalizers();

これらの線は通常、効率を上げるという根拠の下でオブジェクトを破壊すると考えられているメソッドに表示されます。私はこの発言をしました:

  1. すべてのオブジェクトの破棄時にガベージコレクションを明示的に呼び出すと、パフォーマンスが低下します。これは、CLRのパフォーマンスに絶対に必要な場合は考慮されないためです。
  2. これらの命令をこの順序で呼び出すと、他のオブジェクトがファイナライズされている場合にのみ、すべてのオブジェクトが破棄されます。したがって、個別に破棄できるオブジェクトは、実際に必要なく、別のオブジェクトの破棄を待たなければなりません。
  3. デッドロックを生成する可能性があります(参照: この質問

1、2、3は正しいですか?あなたの答えを裏付ける参考資料を教えてください。

私の発言についてはほぼ確実ですが、なぜこれが問題なのかをチームに説明するために、私の主張を明確にする必要があります。それが私が確認と参照を求めている理由です。

32
JPCF

簡単に言えば、それを取り出してください。そのコードは、パフォーマンスをほとんど決して向上させません、または長期的なメモリ使用。

あなたの主張はすべて真実です。 (それはcanデッドロックを生成します;これは常にそれが意味するわけではありませんwillGC.Collect()を呼び出すと収集されますすべてのGC世代のメモリ。これは2つのことを行います。

  • すべての世代にわたって毎回収集-デフォルトでGCが実行する代わりに、世代がいっぱいになったときにのみ世代を収集します。一般的な使用法では、Gen0が(おおよそ)Gen1の10倍の頻度で収集され、Gen1がGen2の(おおよそ)10倍の頻度で収集されます。このコードは、すべての世代を毎回収集します。 Gen0の収集は通常100ms以下です。 Gen2はさらに長くなる可能性があります。
  • 収集不可能なオブジェクトを次世代に昇格させます。つまり、コレクションを強制しても、何らかのオブジェクトへの参照がまだある場合は常に、そのオブジェクトは次の世代に昇格されます。通常、これは比較的まれにしか発生しませんが、以下のようなコードはこれをはるかに頻繁に強制します。

    _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生成にプロモートします。

28
Dan Puzey

理解しやすいポイントがあります。GCを実行すると、実行ごとに多くのオブジェクトが自動的にクリーンアップされます(たとえば、10000)。破棄のたびに呼び出すと、実行ごとに約1つのオブジェクトがクリーンアップされます。

GCはオーバーヘッドが高い(スレッドを停止および開始する必要があるため、すべてのオブジェクトを生きたままスキャンする必要がある)ため、呼び出しをバッチ処理することをお勧めします。

また、すべてのオブジェクトの後にクリーンアップからどのgoodが出てくる可能性がありますか?これはバッチ処理よりもどのように効率的でしょうか?

8
usr

ポイント番号3は技術的には正しいですが、ファイナライザの間に誰かがロックした場合にのみ発生します。

この種の呼び出しがなくても、ファイナライザ内のロックは、ここにあるものよりもさらに悪いです。

GC.Collect()を呼び出すとパフォーマンスが向上する場合があります。

これまでのところ、私のキャリアの中で2回、おそらく3回行っています。 (または、私が行った場所を含めて結果を測定し、もう一度取り出した場合、おそらく5〜6回です。これは、実行する必要があるものです常に実行後に測定します)。

短期間に数百または数千メガのメモリを使い続け、その後、メモリの使用量をはるかに少ないメモリに長期間切り替えると、大規模な、または重大な改善になる可能性があります。明示的に収集します。ここで何が起こっているのですか?

他のどこでも、彼らはせいぜい遅くして、より多くのメモリを使用するでしょう。

6
Jon Hanna

私の他の答えをここで見てください:

GCに収集するかどうか

自分でGC.Collect()を呼び出すと、2つのことが発生する可能性があります。結局、コレクションの実行にmore時間を費やすことになります(手動のGCに加えて、通常のバックグラウンドコレクションが引き続き行われるためです。 Collect())を実行すると、メモリlongerに留まります(そこに移動する必要のないものをより高次の世代に強制したため)。つまり、GC.Collect()を自分で使用することは、ほとんどの場合悪い考えです。

自分でGC.Collect()を呼び出す必要があるのは、ガベージコレクターが知るのが難しいプログラムに関する特定の情報があるときだけです。正規の例は、明確なビジーで軽い負荷サイクルを持つ長期実行プログラムです。負荷の軽い期間の終わり近くに、ビジーサイクルの前に強制的にコレクションを強制して、ビジーサイクルに対してリソースができるだけ解放されるようにすることができます。しかし、ここでも、アプリの構築方法を再考することで、より良い結果が得られる可能性があります(つまり、スケジュールされたタスクがうまく機能するでしょうか?)。

5
Joel Coehoorn

これは1回だけ使用しました。CrystalReportドキュメントのサーバー側キャッシュをクリーンアップするためです。 Crystal Reports Exceptionの私の応答を参照してください:システム管理者が構成したレポート処理ジョブの最大数に達しました

オブジェクトが適切にクリーンアップされないことがあったため、WaitForPendingFinalizersは特に役立ちました。 Webページでのレポートのパフォーマンスが比較的遅いことを考えると、GCによるわずかな遅延は無視でき、メモリ管理の改善により、サーバー全体がより快適になりました。

2
gojimmypi

@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)");
        }
    }

これを調査するために多くの時間を費やしたので、これが他の誰かの助けになることを願っています。

2
Justin

ここの答えに広く同意します-ネイティブのpython実装は比較的効果的で予測可能であるため、独自のガベージコレクションを実行する必要があることはほとんどありません。問題を自分の手に取り入れるのは理にかなっています:

  1. ほぼリアルタイムのアプリケーションがあり、デフォルトのガベージコレクションが不都合なときにトリガーされています。また、Python)でリアルタイム対応のコードを作成している理由も質問する必要があります。
  2. あなたはメモリリークをデバッグしています-メモリ使用量をキャプチャする直前にガベージコレクションを行い、興味深いオブジェクトを探すことで混乱を減らします。ただし、最も基本的な機能以外では、おそらく既存のライブラリを使用した方がよいでしょう。
0
F1Rumors