27

質問

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

私の結果をここであなたと共有したいと思います (かなり興味深いと思います)。お気軽にコメントおよび/または間違いを指摘してください。コードは、 pastebin.com
/is7UaiMV で表示できます。

ノート

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

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

  • Java はA.get(B)B.put(A)として実装しているため、バルク取得バッファ -> バッファはありません。したがって、結果はバルクプットの結果と同じになります。

結論

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

バッファーは、次の両方が当てはまる場合にのみ使用する必要があります。

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

backened-buffer には、バッファの内容を裏付ける Java Array があることに注意してください。put/get をループする代わりに、このバックバッファで操作を行うことをお勧めします。

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

詳細については、こちらを参照してください。

サンプル結果

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

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

-- 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%

100,000回の反復でサイズ1,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%

100,000回の反復でサイズ10,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%
4

2 に答える 2

11

ダイレクト バッファは、Java コードからのアクセスを高速化するためのものではありません。(それが可能な場合、JVM 自体の配列の実装に何か問題がありました。)

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

ところで、バイト バッファ、特にダイレクト バイト バッファへのアクセス速度を測定する場合は、ビューを取得する前にバイトオーダーをネイティブバイト オーダーに変更する価値があります。FloatBuffer

FloatBuffer bufferD = ByteBuffer.allocateDirect(SIZE * 4)
                                .order(ByteOrder.nativeOrder())
                                .asFloatBuffer();
于 2013-09-20T09:43:29.003 に答える
4

名前:

効率的な高速I/Oを実行する必要がある場合にのみ、ダイレクト バッファーを使用します。

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

デフォルトの配列でバッファのような操作を行う必要があり、遅くしてもよい場合は、配列に基づくバッファを使用します。

TSDR:

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

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

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

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

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

あなたの結論は次のようにも述べています。

backened-buffer には、バッファの内容を裏付ける Java Array があることに注意してください。put/get をループする代わりに、このバックバッファで操作を行うことをお勧めします。

これは本当ですが、配列に基づくバッファーの要点全体が欠けています。Array-backed buffers は、arrays の上のファサード パターンです。内部的に配列を使用する必要があるため、配列を使用したバッファーは配列自体よりも高速になることはありません。

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

また読む:

于 2014-08-24T14:36:20.760 に答える