111

何百万もの小さなオブジェクトを作成 (およびリリース) するための「ベスト プラクティス」は何ですか?

私は Java でチェス プログラムを書いています。検索アルゴリズムは、可能な手ごとに 1 つの「Move」オブジェクトを生成します。名目上の検索では、1 秒あたり 100 万を超える Move オブジェクトを簡単に生成できます。JVM GC は私の開発システムの負荷を処理することができましたが、次のような代替アプローチを検討することに興味があります。

  1. ガベージ コレクションのオーバーヘッドを最小限に抑えます。
  2. ローエンド システムのピーク メモリ フットプリントを削減します。

オブジェクトの大部分は非常に短命ですが、生成された移動の約 1% は永続化され、永続化された値として返されます。そのため、プーリングまたはキャッシュの手法では、特定のオブジェクトが再利用されないようにする機能を提供する必要があります。 .

完全に完成されたサンプルコードは期待していませんが、さらに読む/研究するための提案、または同様の性質のオープンソースの例をいただければ幸いです。

4

13 に答える 13

47

詳細ガベージ コレクションを使用してアプリケーションを実行します。

java -verbose:gc

そして、それが集まると教えてくれます。スイープには、高速スイープとフル スイープの 2 種類があります。

[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]

矢印はサイズ前後です。

完全な GC ではなく GC を実行している限り、問題はありません。通常の GC は「若い世代」のコピー コレクターであるため、参照されなくなったオブジェクトは単純に忘れられます。これはまさにあなたが望むことです。

Java SE 6 HotSpot Virtual Machine Garbage Collection Tuningを読むと、おそらく役に立ちます。

于 2013-05-07T13:04:19.620 に答える
11

値オブジェクトのみ (つまり、他のオブジェクトへの参照がない) があり、本当にたくさんのオブジェクトがある場合ByteBuffers、ネイティブのバイト順で直接使用できます [後者は重要です]。数百行の割り当て/再利用するコード + ゲッター/セッター。ゲッターは似ていますlong getQuantity(int tupleIndex){return buffer.getLong(tupleInex+QUANTITY_OFFSSET);}

一度だけ、つまり巨大なチャンクを割り当ててから自分でオブジェクトを管理する限り、GC の問題はほぼ完全に解決されます。参照の代わりに、渡す必要があるintへのインデックス (つまり ) のみを使用します。ByteBufferメモリの調整も自分で行う必要がある場合があります。

テクニックは を使用するように感じますC and void*が、いくつかのラップで耐えられます. パフォーマンスのマイナス面は、コンパイラがそれを排除できなかった場合の境界チェックである可能性があります。ベクトルのようにタプルを処理する場合の主な利点は局所性です。オブジェクト ヘッダーがないため、メモリ フットプリントも削減されます。

それ以外は、事実上すべての JVM の若い世代が簡単に死んでしまい、割り当てコストが単なるポインター バンプにすぎないため、このようなアプローチは必要ない可能性があります。一部のプラットフォーム (つまり ARM/Power) ではメモリ フェンスが必要なため、フィールドを使用すると割り当てコストが少し高くなる可能性がありますがfinal、x86 では無料です。

于 2013-05-08T07:56:01.047 に答える
8

GC が問題であることがわかったと仮定すると (他の人はそうではないかもしれないと指摘しています)、特殊なケース、つまり大規模なチャーンに苦しむクラスのために独自のメモリ管理を実装することになります。オブジェクト プーリングを試してみてください。それがうまく機能するケースを見てきました。オブジェクト プールの実装はよく踏まれた道なので、ここに再度アクセスする必要はありません。次の点に注意してください。

  • マルチスレッド:スレッドローカルプールを使用すると、あなたのケースでうまくいくかもしれません
  • バッキング データ構造: ArrayDeque の使用を検討してください。これは、削除時に適切に実行され、割り当てのオーバーヘッドがないためです。
  • プールのサイズを制限してください:)

測定前/後など

于 2013-05-07T21:24:49.067 に答える
6

同様の問題に遭遇しました。まず、小さなオブジェクトのサイズを小さくしてみてください。各オブジェクト インスタンスでそれらを参照するいくつかのデフォルト フィールド値を導入しました。

たとえば、MouseEvent には Point クラスへの参照があります。新しいインスタンスを作成する代わりに、ポイントをキャッシュして参照しました。たとえば、空の文字列についても同じです。

別のソースは、1 つの int に置き換えられた複数のブール値であり、各ブール値に対して、int の 1 バイトのみを使用します。

于 2013-05-07T13:59:59.073 に答える
1

このような検索アルゴリズムに対して私が使用した 1 つの解決策は、Move オブジェクトを 1 つだけ作成し、それを新しい Move で変更してから、スコープを離れる前に Move を元に戻すことです。一度に 1 つの動きだけを分析し、最適な動きをどこかに保存している可能性があります。

なんらかの理由でそれが実現できず、ピーク時のメモリ使用量を減らしたい場合は、メモリ効率に関する優れた記事をご覧ください: http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-effective-java-チュートリアル.pdf

于 2013-05-11T00:06:35.660 に答える
0

私は GC の大ファンではないので、常にそれを回避する方法を見つけようとしています。この場合、オブジェクト プール パターンを使用することをお勧めします。

アイデアは、後で再利用できるようにスタックに格納することで、新しいオブジェクトの作成を回避することです。

Class MyPool
{
   LinkedList<Objects> stack;

   Object getObject(); // takes from stack, if it's empty creates new one
   Object returnObject(); // adds to stack
}
于 2013-05-12T14:29:49.360 に答える