ByteBuffer.allocate()とByteBuffer.allocateDirect()


144

allocate()たりとallocateDirect()、疑問です。

ここ数年、私はDirectByteBuffersがOSレベルでのダイレクトメモリマッピングであるため、sよりget / put呼び出しの方が速く実行できるという考えに固執していHeapByteBufferます。私は今まで、状況に関する正確な詳細を知ることに本当に興味がありませんでした。2つのタイプのByteBufferのどちらがより高速で、どのような条件であるかを知りたい。


特定の答えを出すには、彼らと何をしているかを具体的に言う必要があります。一方が常に他方よりも速い場合、なぜ2つのバリアントがあるのでしょうか。おそらく、「正確な詳細を見つけることに本当に関心がある」理由を拡張できます。ところで、DirectByteBufferのコードを読みましたか?
Peter Lawrey、2011

これらはSocketChannel、非ブロッキング用に構成されたの読み取りおよび書き込みに使用されます。したがって、@ bmarguliesが言ったことに関して、DirectByteBuffersはチャネルに対してより高速に実行されます。

@Gnarly少なくとも私の回答の現在のバージョンでは、チャネルは利益をもたらすと期待されています。
bmargulies

回答:


150

ロンヒッチズの著書 『Java NIO』は、私があなたの質問に適切な答えになると私が考えたものを提供しているようです。

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

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

通常、ダイレクトバイトバッファは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実装は、バッファキャッシングまたは他の最適化を実行できる場合があります。これにより、不要な労力をかけずに、必要なパフォーマンスを得ることができます。


9
推測が多すぎるため、この引用は好きではありません。また、JVMは、直接でないByteBufferに対してIOを実行するときに、直接ByteBufferを割り当てる必要はありません。ヒープ上に一連のバイトをmallocし、IOを実行し、バイトからByteBufferにコピーして、バイトを解放するだけで十分です。これらの領域はキャッシュすることもできます。しかし、これにJavaオブジェクトを割り当てる必要はまったくありません。実際の答えは測定からのみ得られます。前回測定したとき、大きな違いはありませんでした。特定の詳細をすべて出すには、テストをやり直す必要があります。
Robert Klemme、2011年

4
NIO(およびネイティブの操作)について説明している本に確実性があるかどうかは疑問です。結局のところ、JVMやオペレーティングシステムが異なれば、管理方法も異なるため、特定の動作を保証できないことで作者を非難することはできません。
マーティンTuskevicius 2013

@ RobertKlemme、+ 1、私たちはみな推測を嫌いますが、メジャーOSが多すぎるため、すべてのメジャーOSのパフォーマンスを測定することは不可能かもしれません。別の投稿でも 試みましたが、「OSによって結果が大きく変動する」ことから、ベンチマークに関する多くの問題が見られます。また、すべてのI / Oでバッファコピーなどの恐ろしいことを行う黒い羊がいる場合はどうでしょうか。次に、その羊が原因で、これらの最悪のシナリオを回避するためだけに、本来使用するはずのコードを書かざるを得ない場合があります。
Pacerier 2014

@RobertKlemme同意する。ここでは当て推量が多すぎます。たとえば、JVMがバイト配列をまばらに割り当てる可能性はほとんどありません。
ローン侯爵

@Edwin Dalorzo:実世界でこのようなバイトバッファーが必要なのはなぜですか?それらはプロセス間でメモリを共有するためのハックとして発明されましたか?たとえば、JVMがプロセス上で実行され、ネットワークまたはデータリンクレイヤー上で実行される別のプロセスであるとします-データの送信を担当するこれらのバイトバッファーは、これらのプロセス間でメモリを共有するために割り当てられていますか?私が間違っている場合は私を修正してください..
トムテイラー

25

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


確かに。同様に、Scala / JavaでIOを実行し、アルゴリズム処理のために大容量のメモリデータを使用して組み込みPython /ネイティブライブラリを呼び出す必要がある場合や、TensorflowのGPUにデータを直接フィードする場合などです。
SemanticBeeng

21

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

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

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

結論は前提から来ていません。premissはfalseです。結論も間違っています。あなたは同じから読み書きしている場合、彼らはあなたがJNI層の内側に得ればより高速であり、DirectByteBuffer彼らは多くのデータが全くJNIの境界を横断していないため、より速く。


7
これは良い重要なポイントです。IOのパスでは、ある時点でJava-JNIの境界を越える必要があります。直接および非直接バイトバッファーは境界のみを移動します。直接バッファーでは、Javaランドからのすべてのput操作が交差する必要がありますが、非直接バッファーではすべてのIO操作が交差する必要があります。より速いものはアプリケーションに依存します。
Robert Klemme、2011年

@RobertKlemme要約が正しくありません。すべてのバッファで、Javaとの間でやり取りされるデータはすべてJNI境界を越える必要があります。ダイレクトバッファのポイントは、データを1つのチャネルから別のチャネルにコピーするだけの場合(ファイルのアップロードなど)、Javaにデータを取り込む必要がないため、はるかに高速です。
ローンの侯爵

私の要約はどこが正確ですか?そして、何から始めればよいのでしょうか?私は明示的に「Javaランドからの操作の操作」について話していました。チャネル間でデータをコピーするだけの場合(つまり、Javaランドでデータを処理する必要がない場合)は、もちろん別の話です。
Robert Klemme、2012年

@RobertKlemme「ダイレクトバッファを使用する場合、Javaランドからのすべてのputオペレーションはクロスする必要がある」というステートメントは正しくありません。ゲットとプットの両方がクロスする必要があります。
ローン侯爵2014年

EJPさん、@ RobertKlemmeが1つのフレーズで「put operations」という単語を使用し、文の対照的なフレーズで「IO operations」という単語を使用することを選択することで、意図した区別がまだ明らかにされていないようです。後者の句では、彼の意図は、バッファと何らかの種類のOS提供デバイスとの間の操作を参照することでした。
naki 2017

18

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

出典:


ありがとう。回答は受け付けますが、パフォーマンスの違いに関するより具体的な詳細を探しています。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.