web-dev-qa-db-ja.com

ByteBuffer.allocate()とByteBuffer.allocateDirect()

allocate()またはallocateDirect()に、それが問題です。

ここ数年、私はDirectByteBuffersがOSレベルでの直接メモリマッピングであるため、get [put]呼び出しでHeapByteBuffersよりも速く実行されるという考えに固執しました。今まで状況に関する正確な詳細を見つけることに本当に興味はなかった。 2つのタイプのByteBuffersのどちらがより高速で、どのような条件にあるかを知りたいです。

133
user238033

Ron Hitchesは彼の素晴らしい本の中で Java NIO はあなたの質問に対する良い答えだと思ったものを提供しているようです:

オペレーティングシステムは、メモリ領域でI/O操作を実行します。これらのメモリ領域は、オペレーティングシステムに関する限り、連続したバイトシーケンスです。この場合、バイトバッファのみがI/O操作に参加する資格があることは驚くことではありません。また、オペレーティングシステムがプロセス(この場合はJVMプロセス)のアドレススペースに直接アクセスして、データを転送することを思い出してください。つまり、I/O動作のターゲットであるメモリ領域は、連続したバイトシーケンスである必要があります。 JVMでは、バイトの配列がメモリに連続して格納されない場合があります。そうしないと、ガベージコレクターはいつでもそれを移動できます。配列はJavaのオブジェクトであり、そのオブジェクト内にデータを保存する方法は、JVMの実装ごとに異なる場合があります。

このため、直接バッファの概念が導入されました。ダイレクトバッファは、チャネルおよびネイティブI/Oルーチンとの相互作用を目的としています。ネイティブコードを使用してオペレーティングシステムにメモリ領域を直接ドレインまたはフィルするように指示することにより、チャネルが直接または生のアクセスに使用できるメモリ領域にバイト要素を格納するために最善を尽くします。

通常、I/O操作にはダイレクトバイトバッファが最適です。設計上、JVMで利用可能な最も効率的なI/Oメカニズムをサポートしています。間接バイトバッファはチャネルに渡すことができますが、そうするとパフォーマンスが低下する可能性があります。通常、非直接バッファをネイティブI/O操作のターゲットにすることはできません。書き込みのために非直接ByteBufferオブジェクトをチャネルに渡すと、チャネルは各呼び出しで次のことを暗黙的に行う場合があります。

  1. 一時的な直接ByteBufferオブジェクトを作成します。
  2. 非直接バッファーの内容を一時バッファーにコピーします。
  3. 一時バッファを使用して低レベルのI/O操作を実行します。
  4. 一時バッファオブジェクトはスコープ外になり、最終的にガベージコレクションされます。

これにより、すべてのI/Oでバッファコピーとオブジェクトチャーンが発生する可能性がありますが、これはまさに避けたいことです。ただし、実装によっては、これが悪くない場合があります。ランタイムは、直接バッファをキャッシュして再利用するか、スループットを向上させる他の巧妙なトリックを実行する可能性があります。一度だけ使用するバッファを作成するだけであれば、その違いはそれほど大きくありません。一方、高パフォーマンスのシナリオでバッファーを繰り返し使用する場合は、直接バッファーを割り当てて再利用することをお勧めします。

直接バッファはI/Oに最適ですが、非直接バイトバッファよりも作成に費用がかかる場合があります。ダイレクトバッファで使用されるメモリは、標準のJVMヒープをバイパスして、ネイティブのオペレーティングシステム固有のコードを呼び出すことにより割り当てられます。ホストのオペレーティングシステムとJVMの実装によっては、直接バッファーのセットアップと破棄は、ヒープ常駐バッファーよりもかなり高価になる可能性があります。ダイレクトバッファのメモリストレージ領域は、標準のJVMヒープの外側にあるため、ガベージコレクションの対象ではありません。

直接バッファーと非直接バッファーを使用する場合のパフォーマンスのトレードオフは、JVM、オペレーティングシステム、およびコード設計によって大きく異なります。ヒープの外側にメモリを割り当てることにより、JVMが認識しない追加の力にアプリケーションがさらされる可能性があります。追加の可動部品を使用する場合は、目的の効果が得られていることを確認してください。古いソフトウェアの格言をお勧めします。最初に動作させ、次に高速にします。最適化を事前に心配する必要はありません。最初に正確さに集中してください。 JVM実装は、バッファキャッシングまたはその他の最適化を実行できるため、不必要な労力をあまりかけることなく、必要なパフォーマンスを実現できます。

144
Edwin Dalorzo

ダイレクトバッファがアクセスのために高速になると期待する理由はありませんinside the jvm。それらの利点は、あらゆる種類のチャネルの背後にあるコードなど、ネイティブコードに渡すときに得られます。

25
bmargulies

directByteBuffersはOSレベルでの直接メモリマッピングであるため

そうではありません。これらは単なる通常のアプリケーションプロセスメモリですが、Java GC中の再配置の影響を受けないため、JNIレイヤー内の処理が大幅に簡素化されます。説明する内容は、MappedByteBufferに適用されます。

get/put呼び出しでより高速に実行されること

結論は前提からは続きません。前提は偽です。結論も間違っています。 JNIレイヤー内に入ると高速になり、同じDirectByteBufferから読み書きする場合はmuchより高速になります。これは、データがJNI境界をまったく超える必要がないためです。 。

17
user207421

独自の測定を行うのが最善です。簡単な答えは、サイズに応じて、allocateDirect()バッファからの送信にかかる時間は、allocate()バリアント(/ dev/nullへのファイルのコピーとしてテスト)よりも25%から75%短いですが、割り当て自体は大幅に遅くなる可能性があります(100倍になっても)。

ソース:

17
Raph Levien