web-dev-qa-db-ja.com

アレイ、バッファ、またはダイレクトバッファを使用する場合

質問

OpenGLライブラリで使用するMatrixクラスを作成しているときに、Java配列またはバッファ戦略を使用してデータを格納するかどうか)という質問に遭遇しました(JOGLはMatrix演算用の直接バッファコピーを提供します) 。これを分析するために、配列とバッファと直接バッファのループ演算とバルク演算の相対速度を比較する小さなパフォーマンステストプログラムを作成しました。

ここで私の結果をあなたと共有したいと思います(私はそれらがかなり面白いと思うので)。コメントや間違いを指摘してください。
コードは Pastebin.com/is7UaiMV で見ることができます。

ノート

  • ループ読み取り配列はA [i] = B [i]として実装されます。そうでない場合、JITオプティマイザーはそのコードを完全に削除します。実際のvar = A [i]はほとんど同じようです。

  • 配列サイズが10,000のサンプル結果では、JITオプティマイザーがループ配列アクセスをSystem.arraycopyのような実装に置き換えた可能性が非常に高いです。

  • JavaA.get(B)asBを実装するので、bulk-get buffer-> bufferはありません。 put(A)、したがって、結果はバルクプットの結果と同じになります。

結論

ほとんどすべての状況で、Java内部配列を使用することを強くお勧めします。書き込み/取得速度が大幅に高速になるだけでなく、JITは最終的なコードではるかに優れた最適化を実行できます。 。

bothの場合、バッファはonlyのみ使用する必要があります。

  • 大量のデータを処理する必要があります。
  • そのデータはほとんどまたは常に一括処理されます。

バックバッファには、バッファの内容をバックアップするJava配列があることに注意してください。put/ getをループするのではなく、このバックバッファで操作を実行することをお勧めします。

メモリ使用量が心配な場合は、直接バッファをのみ使用する必要があります。基になるデータにアクセスします。これらは非直接バッファよりもわずかに遅く、基になるデータにアクセスする場合ははるかに遅くなりますが、使用するメモリは少なくなります。さらに、ダイレクトバッファを使用する場合、バイト以外のデータ(float-arrayなど)をバイトに変換するときに余分なオーバーヘッドが発生します。

詳細については、こちらをご覧ください。

サンプル結果

注:パーセンテージは読みやすくするためのものであり、実際の意味はありません。

サイズ16の配列を10,000,000回の反復で使用...

-- Array tests: -----------------------------------------

Loop-write array:           87.29 ms  11,52%
Arrays.fill:                64.51 ms   8,51%
Loop-read array:            42.11 ms   5,56%
System.arraycopy:           47.25 ms   6,23%

-- Buffer tests: ----------------------------------------

Loop-put buffer:           603.71 ms  79,65%
Index-put buffer:          536.05 ms  70,72%
Bulk-put array->buffer:    105.43 ms  13,91%
Bulk-put buffer->buffer:    99.09 ms  13,07%

Bulk-put bufferD->buffer:   80.38 ms  10,60%
Loop-get buffer:           505.77 ms  66,73%
Index-get buffer:          562.84 ms  74,26%
Bulk-get buffer->array:    137.86 ms  18,19%

-- Direct buffer tests: ---------------------------------

Loop-put bufferD:          570.69 ms  75,29%
Index-put bufferD:         562.76 ms  74,25%
Bulk-put array->bufferD:   712.16 ms  93,96%
Bulk-put buffer->bufferD:   83.53 ms  11,02%

Bulk-put bufferD->bufferD: 118.00 ms  15,57%
Loop-get bufferD:          528.62 ms  69,74%
Index-get bufferD:         560.36 ms  73,93%
Bulk-get bufferD->array:   757.95 ms 100,00%

サイズ1,000の配列を100,000回の反復で使用...

-- Array tests: -----------------------------------------

Loop-write array:           22.10 ms   6,21%
Arrays.fill:                10.37 ms   2,91%
Loop-read array:            81.12 ms  22,79%
System.arraycopy:           10.59 ms   2,97%

-- Buffer tests: ----------------------------------------

Loop-put buffer:           355.98 ms 100,00%
Index-put buffer:          353.80 ms  99,39%
Bulk-put array->buffer:     16.33 ms   4,59%
Bulk-put buffer->buffer:     5.40 ms   1,52%

Bulk-put bufferD->buffer:    4.95 ms   1,39%
Loop-get buffer:           299.95 ms  84,26%
Index-get buffer:          343.05 ms  96,37%
Bulk-get buffer->array:     15.94 ms   4,48%

-- Direct buffer tests: ---------------------------------

Loop-put bufferD:          355.11 ms  99,75%
Index-put bufferD:         348.63 ms  97,93%
Bulk-put array->bufferD:   190.86 ms  53,61%
Bulk-put buffer->bufferD:    5.60 ms   1,57%

Bulk-put bufferD->bufferD:   7.73 ms   2,17%
Loop-get bufferD:          344.10 ms  96,66%
Index-get bufferD:         333.03 ms  93,55%
Bulk-get bufferD->array:   190.12 ms  53,41%

サイズ10,000の配列を100,000回の反復で使用...

-- Array tests: -----------------------------------------

Loop-write array:          156.02 ms   4,37%
Arrays.fill:               109.06 ms   3,06%
Loop-read array:           300.45 ms   8,42%
System.arraycopy:          147.36 ms   4,13%

-- Buffer tests: ----------------------------------------

Loop-put buffer:          3385.94 ms  94,89%
Index-put buffer:         3568.43 ms 100,00%
Bulk-put array->buffer:    159.40 ms   4,47%
Bulk-put buffer->buffer:     5.31 ms   0,15%

Bulk-put bufferD->buffer:    6.61 ms   0,19%
Loop-get buffer:          2907.21 ms  81,47%
Index-get buffer:         3413.56 ms  95,66%
Bulk-get buffer->array:    177.31 ms   4,97%

-- Direct buffer tests: ---------------------------------

Loop-put bufferD:         3319.25 ms  93,02%
Index-put bufferD:        3538.16 ms  99,15%
Bulk-put array->bufferD:  1849.45 ms  51,83%
Bulk-put buffer->bufferD:    5.60 ms   0,16%

Bulk-put bufferD->bufferD:   7.63 ms   0,21%
Loop-get bufferD:         3227.26 ms  90,44%
Index-get bufferD:        3413.94 ms  95,67%
Bulk-get bufferD->array:  1848.24 ms  51,79%
23
TwoThe

直接バッファは、Javaコードからのアクセスを高速化することを意図したものではありません。(可能であれば、JVM自体の配列実装に問題がありました。)

これらのバイトバッファは、バイトバッファを ByteChannel に書き込むことができ、前述のOpenGLライブラリなどのネイティブコードと組み合わせて直接バッファを使用できるため、他のコンポーネントとのインターフェイス用です。 。これらのこれらの操作を加速することを目的としています。グラフィックカードのチップをレンダリングに使用すると、Javaコードからのバッファへのアクセスが遅くなる可能性があることを補うよりも、全体的な操作をある程度加速できます。

ちなみに、バイトバッファ、特にダイレクトバイトバッファへのアクセス速度を測定する場合は、バイト順序をnativeバイト順序に変更する価値があります。 FloatBuffer ビューを取得する前:

FloatBuffer bufferD = ByteBuffer.allocateDirect(SIZE * 4)
                                .order(ByteOrder.nativeOrder())
                                .asFloatBuffer();
9
Holger

Tldr:

効率的な高速I/Oを実行する必要がある場合は、直接バッファのみを使用します-)。

効率的な高速非I/O操作が必要な場合は、デフォルトの配列が最適です。

デフォルトの配列でバッファのような操作を行う必要がある場合、そしてslow、次にアレイでバックアップされたバッファを使用します。

Tsdr:

テストでI/O操作がテストされなかったため、結論が間違っています。

あなたの結論は述べています(強調は私のものではありません):

メモリ使用量が心配な場合は、直接バッファをのみ使用する必要があります。基になるデータにアクセスします。これらは非直接バッファよりもわずかに遅く、基になるデータにアクセスする場合ははるかに遅くなりますが、使用するメモリは少なくなります。さらに、ダイレクトバッファを使用する場合、バイト以外のデータ(float-arrayなど)をバイトに変換するときに余分なオーバーヘッドが発生します。

それは明らかに間違っています。 ダイレクトバッファは、メモリの問題ではなく、速度の問題を解決するためのものです。高性能I/Oアクセスが必要な場合は、常に直接バッファを使用する必要があります。これには、ファイル/ネットワーク操作などが含まれます。正しく使用すると間違いなく高速になり、実際にはJava APIはすぐに使用できます。

ファイル/ネットワーク操作を行う場合、バイト以外のデータをバイトに変換するときに余分なオーバーヘッドがあります。これは、直接バッファだけでなく、すべてにも当てはまります。

あなたの結論はまた述べています:

バックバッファには、バッファの内容をバックアップするJava配列があることに注意してください。put/ getをループするのではなく、このバックバッファで操作を実行することをお勧めします。

これは真実ですが、配列に裏打ちされたバッファの要点全体が欠けています。 配列に裏打ちされたバッファは ファサードパターン 配列の上にあります。配列に裏打ちされたバッファは、内部的に配列を使用する必要があるため、配列自体よりも高速になることはありません。

そのため、速度ではなく、利便性のためにあります。つまり、速度が必要な場合は、array-facadeではなくarrayを選択することをお勧めします。利便性/読みやすさが必要な場合は、配列に対するバッファのような操作のために、配列ではなく配列ファサードを選択することをお勧めします。

また読む:

4
Pacerier