2

llvm のドキュメントには次のように書かれています。

ただし、実際には、積極的なガベージ コレクション手法を使用することによる局所性とパフォーマンスの利点が、低レベルの損失よりも優先されます。

では、メモリを手動で管理するのではなく、ガベージ コレクションを使用するとパフォーマンスが向上するのは、正確には何でしょうか? (コード作成時間の明らかな減少に加えて)ヒープ圧縮を実行すると、空間的局所性とキャッシュ使用率が向上するという利点だけですか? または、一度にすべてを削除するなど、もっと役立つものはありますか?

4

4 に答える 4

2

私は Oracle (ex-Sun) と IBM JVM についてしか話せません。それらの効率は、新しく作成されたオブジェクトが非常に長く存続する可能性が低いという事実に依存しています。したがって、それらを独自の領域に分離すると、その領域を頻繁に圧縮できます。生存者が少ないため、安価な操作です。頻繁な圧縮は空き領域を連続的に保つことができることを意味します。そのため、トラバースする空きチェーンがなく、メモリの断片化がないため、オブジェクトの作成も安価になります。

手動のメモリ管理スキームがこれほど効率的であることはめったにありません。これは、アプリケーションごとに再発明される可能性が低い比較的複雑な方法だからです。これらのガベージ コレクターは、個々のアプリケーションがこれまでに受けたよりも多くの労力をかけて、より長い期間にわたって進化し、最適化されてきました。パフォーマンスが大幅に向上しなければ、驚くべきことであり、がっかりすることでしょう。

于 2012-05-17T22:22:19.847 に答える
2

最新のプロセッサでは、メモリ キャッシュが重要です。キャッシュ ミスが発生すると、低速バスがデータを提供するのを待ちながら、数百の CPU サイクルの間プロセッサが停止する可能性があります。

キャッシュを有効にするには、参照の局所性が必要です。つまり、次のメモリ アクセスが前のメモリ アクセスに近い場合、データが既にキャッシュに存在する可能性が高くなります。

ガベージ コレクターは、それをうまく機能させるために大いに役立ちます。大きな利点はコレクションではなく、オブジェクト グラフを再構築し、その間にデータ構造を再編成する機能です。圧縮。

オブジェクトへのポインターの配列である典型的なデータ構造を想像してみてください。たとえば、ファイルから一連の文字列を読み取り、それらをオブジェクトのフィールド値に変換している間、これはゆっくりと構築されています。そうすることで、割り当てられたオブジェクトはアドレス空間に散らばります。文字列のように、ワーカー オブジェクトで区切られた配列が指す長寿命オブジェクト。後でその配列を反復すると、かなり遅くなります。

ガベージ コレクタが実行され、データ構造が再構築されるまで。指し示したすべてのオブジェクトを順番に並べます。

要素 N にアクセスすると、要素 N+1 がすぐに利用できる可能性が非常に高くなるため、コレクションの反復は非常に高速です。L1 キャッシュにない場合は、L2 または L3 の可能性が非常に高くなります (ある場合)。

非常に大きな成果です。ガベージ コレクションを明示的なメモリ管理と競合させた 1 つの機能です。明示的な種類では、ポインターが無効になるため、移動オブジェクトをサポートしないという問題があります。

于 2012-05-17T23:11:54.657 に答える
1

局所性がパフォーマンスにまったく役立つとは思えません-確かに、小さなオブジェクトはヒープの同じ領域に同時に作成される傾向があります(ただし、これはCにも当てはまります)。時間の経過とともに、残っているこれらの小さなオブジェクトは緊密に圧縮されます。ヒープの関連領域であり、Cスタイルの割り当てよりも有利なのはおそらくこれです。ただし、これらの小さなオブジェクトだけを使用するプログラムを見せてください。すべてを実行するプログラムを紹介します。スタックで使用されるすべてのオブジェクトを渡すプログラムを見せてください。スピードで叫ぶものを紹介します。

メモリの割り当て解除は、割り当てを解除する必要がないため、短期的にはパフォーマンス上の利点になります。ただし、ガベージコレクターが起動すると、この利点はなくなります。ただし、通常、収集はシステムで(理論的には)他に何も起きていないときに行われるため、コストは事実上無効になります。

ヒープの圧縮も割り当てに役立ちます。すべての割り当てはヒープの最初から行うことができ、メモリマネージャは、適切なサイズの次の空き領域ブロックを探すためにヒープを歩く必要はありません。ただし、従来のシステムでは、複数の固定ブロックヒープを使用することで同じ速度を得ることができます(つまり、常に必要なブロックのサイズにヒープから割り当て、常に固定ブロックを割り当てるため、ヒープをウォークするだけで最初の空きブロックを見つけます。これはビットマップを使用して削除できます)

したがって、全体として、もちろんベンチマークを除いて、ほとんどメリットはありません。私の経験では、GCは、ユーザーが大量のメモリ割り当てを必要とする新しいページをロードするなどの操作を行ったために、通常はシステムメモリがいっぱいになったときに、ジャンプして劇的に遅くなる可能性があります。 。これにはコレクションが必要でした。

また、大量のメモリを使用する傾向があります。「メモリは安い」がGC言語の信条であるため、プログラムはこれを念頭に置いて作成されます。つまり、メモリの割り当ては、特に一時オブジェクトや中間オブジェクトではるかに一般的です。これがよく知られている証拠については、StringBuilderクラスを参照してください。文字列はこれを使用して「解決」できますが、他の多くのオブジェクトは依然としてワイルド放棄で割り当てられます。大量のメモリを使用するプログラムは、RAM IOに苦労します。使用するメモリはすべてCPUキャッシュに取り込む必要があり、使用するメモリが多いほど、CPUMMが実行する必要のあるIOも多くなります。間違った状況でパフォーマンスを殺します。

さらに、GCが発生した場合は、ファイナライズされたオブジェクトも処理する必要があります。これは以前ほど悪くはありませんが、ファイナライザーの実行中にプログラムを停止する可能性があります。

古いJavaGCはパフォーマンスにとって恐ろしいものでしたが、多くの研究によって大幅に改善されましたが、それでも完全ではありません。

編集:ローカリゼーションについてもう1つ、配列を作成していくつかのアイテムを追加し、次に割り当てのロードを実行してから、別のアイテムを配列に追加することを想像してください-GCシステムでは、追加された配列要素はローカライズされません圧縮後、配列内の各オブジェクトは個別のアイテムとしてヒープに格納されます。これが、ローカリゼーションの問題が、実際に考えられているほど大きな問題ではないと思う理由です。ここで、それをバッファーが割り当てられ、オブジェクトがバッファー・スペース内に割り当てられている配列と比較します。新しいアイテムを追加するには、再割り当てとコピーが必要になる場合がありますが、読み取りと変更は非常に高速です。

于 2012-05-17T22:39:27.690 に答える
0

まだ言及されていない要因の 1 つは、特にマルチスレッド システムでは、どのオブジェクトが他のオブジェクトへの最後に残った参照を保持することになるかを確実に予測することが難しい場合があることです。サイクルを含む可能性のあるオブジェクト グラフについて心配する必要がない場合は、この目的で参照カウントを使用できます。オブジェクトへの参照をコピーする前に、その参照カウントをインクリメントします。オブジェクトへの参照を破棄する前に、その参照カウントを減らします。参照カウントをデクリメントするとゼロになり、オブジェクトと参照が破棄されます。このようなアプローチは、CPU コアが 1 つしかないコンピューターでうまく機能します。任意の時点で実際に実行できるスレッドが 1 つだけである場合、2 つのスレッドが同じオブジェクトを調整しようとした場合に何が起こるかを心配する必要はありません。s 参照カウントを同時に。残念ながら、複数の CPU コアを備えたシステムでは、参照カウントを調整したい CPU は、そのアクションを他のすべての CPU と調整して、2 つの CPU がまったく同時にカウンターにヒットしないようにする必要があります。このような調整は、単一の CPU では「無料」ですが、マルチコア システムでは比較的高価です。

バッチ モードのガベージ コレクターを使用する場合、オブジェクト参照は通常、CPU 間の調整なしで、自由に割り当て、コピー、および破棄できます。定期的にすべての CPU を停止してガベージ コレクション サイクルを実行する必要がありますが、すべての CPU が数秒ごとに相互に調整する必要がある場合は、1 回ごとに相互に調整する必要があるよりもはるかに安価です。オブジェクト参照の割り当て。

于 2012-05-21T21:54:03.217 に答える