9

私のアプリケーションは、大量のバイナリ シリアライゼーションと大きなオブジェクトの圧縮を行います。非圧縮のシリアル化されたデータセットは約 14 MB です。圧縮すると約 1.5 MB になります。データセットで serialize メソッドを呼び出すたびに、大きなオブジェクト ヒープのパフォーマンス カウンターが 1 MB 未満から約 90 MB に跳ね上がることがわかりました。また、比較的負荷の高いシステムでは、通常、このシリアライゼーション プロセスが数回発生するしばらく (数日) 実行した後、このシリアライゼーション メソッドが呼び出されると、アプリケーションがメモリ不足の例外をスローすることが知られています。メモリは十分にあるようです。断片化が問題であると推測しています (100% 確信しているとは言えませんが、ほぼ確実です)。

私が考えることができる最も簡単な短期的な修正(短期と長期の両方の答えを探していると思います)は、シリアル化プロセスが完了した直後に GC.Collect を呼び出すことです。私の意見では、これはLOHからオブジェクトをガベージコレクションし、他のオブジェクトを追加する前に行う可能性があります。これにより、断片化をあまり引き起こすことなく、他のオブジェクトがヒープ内の残りのオブジェクトにぴったりと収まるようになります。

このばかげた 90MB の割り当て以外に、失われた LOH を使用するものは他にないと思います。この 90 MB の割り当ても比較的まれです (約 4 時間ごと)。もちろん、そこにはまだ 1.5 MB の配列があり、おそらく他のいくつかの小さなシリアル化されたオブジェクトがあります。

何か案は?

良い反応の結果として更新

これが仕事をする私のコードです。私は実際にこれを変更して、シリアル化中に圧縮して、シリアル化が同時にストリームにシリアル化されるようにしましたが、あまり良い結果は得られませんでした。また、メモリ ストリームを 100 MB に事前割り当てし、同じストリームを 2 回続けて使用しようとしましたが、とにかく LOH は最大 180 MB になりました。Process Explorer を使用して監視しています。それは非常識です。次に UnmanagedMemoryStream のアイデアを試してみようと思います。

そうでない場合は、試してみることをお勧めします。この正確なコードである必要はありません。大規模なデータセットをシリアル化するだけで、驚くべき結果が得られます (私のものには、約 15 のテーブルがあり、文字列と列がたくさんあります)。

        byte[] bytes;
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer =
        new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();            
        System.IO.MemoryStream memStream = new System.IO.MemoryStream();
        serializer.Serialize(memStream, obj);
        bytes = CompressionHelper.CompressBytes(memStream.ToArray());
        memStream.Dispose();
        return bytes;

UnmanagedMemoryStream でバイナリ シリアル化を試行した後に更新する

UnmanagedMemoryStream にシリアル化しても、LOH は同じサイズにジャンプします。私が何をしても、BinaryFormatter を呼び出してこの大きなオブジェクトをシリアル化すると、LOH が使用されるようです。事前割り当てに関しては、あまり役に立たないようです。100MBを事前に割り当ててからシリアライズすると、170MBが使用されます。これがそのコードです。上記のコードよりもさらに簡単

BinaryFormatter serializer  = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream(1024*1024*100);
GC.Collect();
serializer.Serialize(memoryStream, assetDS);

真ん中の GC.Collect() は、LOH パフォーマンス カウンターを更新するためだけのものです。正しい 100 MB が割り当てられることがわかります。しかし、シリアライズを呼び出すと、すでに割り当てられている 100 の上にそれが追加されているように見えることに気付くでしょう。

4

6 に答える 6

4

コレクション クラスと MemoryStream などのストリームが .NET で動作する方法に注意してください。それらには、基礎となるバッファー、単純な配列があります。コレクションまたはストリーム バッファが配列に割り当てられたサイズを超えると、配列が再割り当てされ、以前のサイズの 2 倍になります。

これにより、LOH で配列のコピーが多数発生する可能性があります。14MB のデータセットは、128KB で LOH の使用を開始し、さらに 256KB、さらに 512KB などを使用します。最後のもの、実際に使用されるものは、約 16MB になります。LOH には、これらの合計が約 30MB 含まれており、そのうちの 1 つだけが実際に使用されています。

gen2 コレクションなしでこれを 3 回実行すると、LOH が 90MB に増加します。

これを回避するには、バッファーを予想されるサイズに事前に割り当てます。MemoryStream には、初期容量を受け取るコンストラクターがあります。すべてのコレクション クラスも同様です。すべての参照を null にした後で GC.Collect() を呼び出すと、LOH の詰まりを解消し、これらの中間バッファーをパージするのに役立ちますが、gen1 および gen2 ヒープがすぐに詰まるという犠牲を払います。

于 2009-12-18T21:51:11.957 に答える
3

残念ながら、これを修正できる唯一の方法は、LOHに大きなチャンクを割り当てないように、データをチャンクに分割することでした。ここで提案されたすべての回答は良好であり、機能することが期待されていましたが、機能しませんでした。.NETでのバイナリシリアル化(.NET 2.0 SP2を使用)は、ユーザーがメモリ割り当てを制御できないようにする、内部で独自の小さな魔法を実行しているようです。

質問に対する答えは、「これはうまくいかない可能性が高い」です。.NETシリアル化を使用する場合、最善の策は、大きなオブジェクトを小さなチャンクにシリアル化することです。他のすべてのシナリオでは、上記の答えは素晴らしいです。

于 2010-03-05T16:01:30.880 に答える
2

90MB の RAM は多くありません。

問題がない限り、GC.Collect の呼び出しは避けてください。問題があり、それ以上の解決策がない場合は、GC.Collect を呼び出して、問題が解決したかどうかを確認してください。

于 2009-12-18T21:48:35.480 に答える
0

ここにある他のポスターのいくつかに同意します。GC.Collectを介して強制的に動作させるのではなく、トリックを使用して.NETFrameworkを操作することをお勧めします。

ガベージコレクターへの圧力を軽減する方法について説明しているこのチャンネル9のビデオが役立つ場合があります。

于 2010-01-14T20:56:03.793 に答える
0

LOH サイズが跳ね上がる心配はありません。LOH の割り当て/割り当て解除について心配します。.Net は LOH について非常に馬鹿げています。LOH オブジェクトを通常のヒープから遠く離れた場所に割り当てるのではなく、次に利用可能な VM ページに割り当てます。LOH と通常のオブジェクトの両方の多くの割り当て/割り当て解除を行う 3D アプリがあります。その結果 (DebugDiag ダンプ レポートに見られるように)、大きなチャンクがなくなるまで、小さなヒープと大きなヒープのページが RAM 全体で交互に表示されます。アプリケーションの 2 GB の VM スペースが残っています。可能な場合の解決策は、必要なものを一度割り当ててから解放せず、次回に再利用することです。

DebugDiag を使用してプロセスを分析します。VM アドレスが 2 GB のアドレス マークに向かって徐々に増加する様子をご覧ください。次に、それが起こらないように変更を加えます。

于 2010-01-14T20:46:35.497 に答える
0

サービスなど、長時間実行する必要があるものに LOH を本当に使用する必要がある場合は、割り当てが解除されず、理想的には起動時に割り当てることができるバッファー プールを使用する必要があります。これは、もちろん、このために「メモリ管理」を自分で行う必要があることを意味します。

このメモリで何をしているかによっては、選択した部分のネイティブ コードに p/Invoke して、LOH で新しく割り当てられたスペースにデータを配置するように強制する .NET API を呼び出さなくても済むようにする必要がある場合もあります。

これは、問題に関する良い出発点の記事です: https://devblogs.microsoft.com/dotnet/using-gc-effectively-part-3/

GC トリックが機能する場合は、非常に幸運だと思います。実際に機能するのは、システムで同時に多くのことが行われていない場合のみです。並行して作業を進めている場合、これは避けられない作業をわずかに遅らせるだけです。

また、GC.Collect.IIRC に関するドキュメントを読んでください。 GC.Collect(n) は、世代 n を超えて収集しないとだけ言っています。実際に世代 n に取得することはありません。

于 2009-12-18T22:05:12.387 に答える