11

何日も何ヶ月も再起動せずに実行するはずの Windows コンソール アプリがあります。アプリは、MSMQ から「作業」を取得して処理します。作業チャンクを同時に処理する 30 のスレッドがあります。

MSMQ からの各ワーク チャンクは約 200 KB で、そのほとんどは 1 つの String オブジェクトに割り当てられます。

これらの作業チャンクを約 3 ~ 4 千処理した後、アプリケーションのメモリ消費量が途方もなく高く、1 ~ 1.5 GB のメモリを消費していることに気付きました。

私はプロファイラーを介してアプリを実行し、このメモリのほとんど (おそらくギグ程度) が大きなオブジェクト ヒープで使用されていないことに気付きましたが、構造は断片化されています。

これらの未使用 (ガベージ コレクション) バイトの 90% が以前に割り当てられた文字列であることがわかりました。私は、MSMQ から入ってくる文字列が割り当てられ、使用され、割り当てが解除されたため、断片化の原因になっているのではないかと疑い始めました。

GC.Collect(2 または GC.Max...) のようなものは、大きなオブジェクト ヒープを gc しますが、圧縮しないため、役に立たないことを理解しています (これが問題です)。したがって、これらの文字列をキャッシュして何らかの方法で再利用する必要があると思いますが、文字列は不変であるため、StringBuilders を使用する必要があります。

私の質問は次のとおりです。基本的な構造を変更せず (つまり、MSMQ を使用してこれを変更することはできません)、LOH の断片化を避けるために毎回新しい文字列を初期化することを回避する方法はありますか?

ありがとう、ヤニス

更新: これらの「作業」チャンクが現在どのように取得されているかについて

現在、これらは MSMQ に WorkChunk オブジェクトとして格納されています。これらの各オブジェクトには、Contents という文字列と、Headers という別の文字列が含まれています。これらは実際のテキスト データです。必要に応じてストレージ構造を別のものに変更したり、必要に応じて基になるストレージ メカニズムを MSMQ 以外のものに変更したりできます。

ワーカーノード側では現在行っています

WorkChunk チャンク = _Queue.Receive();

したがって、この段階でキャッシュできるものはほとんどありません。どういうわけか構造を変更すれば、少し進歩できると思います。いずれにせよ、この問題を解決しなければならないので、何ヶ月もの作業を無駄にしないために必要なことは何でもします。

更新:以下の提案をいくつか試してみたところ、この問題はローカル マシン (Windows 7 x64 および 64 ビット アプリを実行) では再現できないことに気付きました。これは物事を非常に困難にします-誰かが理由を知っていれば、この問題をローカルで解決するのに本当に役立ちます.

4

4 に答える 4

4

問題は、大きなオブジェクト ヒープでのメモリ割り当てが原因のようです。大きなオブジェクト ヒープは圧縮されていないため、断片化の原因となる可能性があります。大きなオブジェクト ヒープの断片化が発生していることを確認するために従うことができるいくつかのデバッグ手順を含む、より詳細な記事がここにあります。

大きなオブジェクト ヒープが発見されました

2 つの3 つの解決策があるようです。

  1. 各チャンクが 85,000 バイト未満のチャンク/短い文字列で処理を実行するようにアプリケーションを変更します。これにより、大きなオブジェクトの割り当てが回避されます。
  2. アプリケーションを変更して、事前にいくつかの大きなメモリ チャンクを割り当て、代わりに割り当てられたメモリに新しいメッセージをコピーして、それらのチャンクを再利用します。バイト配列を使用する場合のヒープの断片化を参照してください。
  3. そのままにしておく - メモリ不足の例外が発生せず、アプリケーションがシステム上で実行されている他のアプリケーションに干渉していない限り、おそらくそのままにしておく必要があります。

ここで、仮想メモリと物理メモリの違いを理解することが重要です。プロセスが大量の仮想メモリを使用していても、割り当てられたオブジェクトの数が比較的少ない場合、そのプロセスの物理メモリの使用量が少ない可能性があります。 (未使用のメモリはディスクにページングされます) つまり、システム上の他のプロセスへの影響はほとんどありません。「VM Hoarding」オプションが役立つ場合もあります。詳細については、記事「Large Object Heap Uncovered」を参照してください。

どちらの変更でも、単一の大きな文字列ではなく、バイト配列と短い部分文字列を使用して処理の一部またはすべてを実行するようにアプリケーションを変更する必要があります。これがどれほど難しいかは、実行している処理の種類によって異なります。 .

于 2011-10-14T11:08:07.170 に答える
2

LOH にフラグメンテーションがある場合、その上にオブジェクトが割り当てられていることを意味します。遅延を許容できる場合は、現在実行中のすべてのタスクが終了するまで時々待って、 を呼び出すことができますGC.Collect()。参照される大きなオブジェクトがない場合、それらはすべて収集され、LOH の断片化が効果的に解消されます。もちろん、これは (ほぼ) すべての大きなオブジェクトが参照されていない場合にのみ機能します。

また、仮想空間がほぼ無制限であるため、64 ビット システムでは断片化によるメモリ不足が問題になる可能性がはるかに低いため、64 ビット OS への移行も役立つ場合があります。

于 2011-10-14T11:25:32.677 に答える
1

おそらく、作業の処理中に使用できる文字列オブジェクトのプールを作成し、終了したら元に戻すことができます。

LOH で大きなオブジェクトが作成されると、それを削除することはできません (私の知る限り)。したがって、これらのオブジェクトの作成を避けられない場合は、それらを再利用することをお勧めします。

両端でプロトコルを変更できる場合は、「Contents」文字列をより小さなセット (それぞれ <80k) に減らすと、LOH に格納されなくなります。

于 2011-10-14T10:32:20.187 に答える
0

String.Intern(...) を使用して重複参照を排除するのはどうですか。パフォーマンスが低下しますが、文字列によっては影響がある場合があります。

于 2012-10-08T19:10:09.533 に答える