何时使用Array,Buffer或direct Buffer

Two*_*The 22 java arrays performance buffer

在编写用于OpenGL库的Matrix类时,我遇到了是否使用Java数组或缓冲区策略来存储数据的问题(JOGL为Matrix操作提供了直接缓冲区复制).为了分析这一点,我编写了一个小型性能测试程序,它比较了Arrays与Buffers vs direct Buffers的循环和批量操作的相对速度.

我想在这里与你分享我的成果(因为我觉得它们很有趣).请随时评论和/或指出任何错误.
可以在pastebin.com/is7UaiMV上查看代码.

笔记

  • 循环读取数组实现为A [i] = B [i],否则JIT优化器将完全删除该代码.实际var = A [i]似乎几乎相同.

  • 在数组大小为10,000的示例结果中,JIT优化器很可能已使用类似System.arraycopy的实现替换了循环数组访问.

  • 没有批量获取缓冲区 - >缓冲区,因为Java将A.get(B)实现B.put(A),因此结果与批量放置结果相同.

结论

在几乎所有情况下,强烈建议使用Java内部数组.不仅提高/获取速度大大加快,JIT还能够对最终代码执行更好的优化.

只有在以下两种情况适用时才应使用缓冲区:

  • 您需要处理大量数据.
  • 该数据大部分或总是经过批量处理.

请注意,后备缓冲区具有Java数组,用于补偿缓冲区的内容.建议在此后​​台缓冲区上执行操作,而不是循环put/get.

只有在担心内存使用情况且永远不会访问基础数据时,应使用直接缓冲区.它们比非直接缓冲区稍慢,如果访问基础数据则要慢得多,但使用的内存较少.此外,在使用直接缓冲区时,将非字节数据(如float-arrays)转换为字节时会产生额外的开销.

有关详细信息,请参阅此处

样本结果

注意:百分比仅为便于阅读而没有实际意义.

使用大小为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%
Run Code Online (Sandbox Code Playgroud)

使用大小为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%
Run Code Online (Sandbox Code Playgroud)

使用大小为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%
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 9

直接缓冲区并不意味着加速Java代码的访问.(如果可能的话,JVM自己的数组实现有问题.)

这些字节缓冲区用于与其他组件连接,因为您可以将字节缓冲区写入a ByteChannel,您可以将直接缓冲区与本机代码结合使用,例如您提到的OpenGL库.它旨在加速这些操作.使用图形卡的芯片进行渲染可以加速整体操作,而不仅仅是补偿从Java代码中缓慢访问缓冲区的程度.

顺便说一句,如果你测量字节缓冲区的访问速度,特别是直接字节缓冲区,在获取视图之前,值得将字节顺序更改为本字节顺序FloatBuffer:

FloatBuffer bufferD = ByteBuffer.allocateDirect(SIZE * 4)
                                .order(ByteOrder.nativeOrder())
                                .asFloatBuffer();
Run Code Online (Sandbox Code Playgroud)


Pac*_*ier 5

总得:

当我们需要进行高效的高速I/O时才使用直接缓冲区。

如果我们需要高效高速的非I/O操作,默认数组是最好的选择。

如果我们需要在默认数组上执行类似缓冲区的操作,并且我们可以承受较慢的速度,那么请使用数组支持的缓冲区。

总账:

您的测试没有测试任何 I/O 操作,因此结论是错误的。

你的结论是(重点不是我的):

当您担心内存使用并且从不访问底层数据时才应使用直接缓冲区。它们比非直接缓冲区稍慢,如果访问底层数据则慢得多,但使用更少的内存。此外,在使用直接缓冲区时将非字节数据(如浮点数组)转换为字节时会产生额外的开销。

这显然是错误的。直接缓冲区旨在解决速度问题,而不是内存问题。每当需要高性能I/O 访问时,就应该使用直接缓冲区。这包括文件/网络操作等。如果使用正确,它肯定会更快,而且实际上是 Java API 提供的开箱即用的最快速度。

在进行文件/网络操作时,将非字节数据转换为字节时会产生额外的开销。这对于所有事物都是如此,而不仅仅是直接缓冲区。

你的结论还指出:

请注意,后备缓冲区有一个支持缓冲区内容的 Java 数组。建议在此后​​台缓冲区上执行操作,而不是循环 put/get。

这是事实,但您错过了数组支持的缓冲区的全部要点。数组支持的缓冲区是数组之上的外观模式。数组支持的缓冲区永远不会比数组本身更快,因为它们在内部必须使用数组。

因此,它们的存在是为了方便,而不是为了速度。换句话说,如果您需要速度,建议选择数组而不是数组外观。如果您需要方便/可读性,建议选择数组外观而不是数组来对数组进行类似缓冲区的操作。

另请阅读:

  • @TwoThe,我的帖子已经将事实摆在桌面上。请参阅我提供的链接中链接的链接:例如 bug 数据库中的线程 http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4879883 。另外,为什么说*Java NIO*这本书不真实呢? (2认同)