在编写用于OpenGL库的Matrix类时,我遇到了是否使用Java数组或缓冲区策略来存储数据的问题(JOGL为Matrix操作提供了直接缓冲区复制)。为了分析这一点,我编写了一个小型性能测试程序,它比较了Arrays与Buffers与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)转换为字节时会产生额外的开销。
有关详细信息,请参阅此处:
注意:百分比只是为了便于阅读而且没有实际意义。
-- 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%
-- 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%
-- 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%
答案 0 :(得分:9)
直接缓冲区并不意味着加速Java代码的访问。 (如果可能的话,JVM自己的数组实现有问题。)
这些字节缓冲区用于与其他组件连接,因为您可以将字节缓冲区写入ByteChannel
,并且可以将直接缓冲区与本机代码结合使用,例如您提到的OpenGL库。它旨在加速这些操作。使用图形卡的芯片进行渲染可以加速整体操作,而不仅仅是补偿从Java代码中缓慢访问缓冲区。
顺便说一下,如果你测量字节缓冲区的访问速度,特别是直接字节缓冲区,在获取FloatBuffer
之前,值得将字节顺序更改为 native 字节顺序。视图:
FloatBuffer bufferD = ByteBuffer.allocateDirect(SIZE * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
答案 1 :(得分:3)
如果我们需要执行高效的高速 I / O ,请使用直接缓冲 。
如果我们需要高效的非I / O 操作,默认数组是最佳选择。
如果我们需要在默认数组上执行类似缓冲区的操作,我们可以慢 ,那么使用数组支持的缓冲区。
您的测试没有测试任何I / O操作,因此它的结论是错误的。
你的结论陈述(强调不是我的):
如果您担心内存,则应使用仅直接缓冲区 用法,永远不会访问基础数据。它们稍慢 比非直接缓冲区,如果基础数据是慢得多 访问,但使用更少的内存。此外还有额外的开销 将非字节数据(如float-arrays)转换为字节时 使用直接缓冲区。
这显然是错误的。 直接缓冲区旨在解决速度问题,而非内存问题。只要您需要高性能的 I / O访问,就应该使用直接缓冲区。这包括文件/网络操作等。正确使用时肯定更快,实际上是Java API提供的最快的开箱即用。
进行文件/网络操作时,将非字节数据转换为字节时会产生额外的开销。这对于所有都是如此,而不仅仅是直接缓冲区。
您的结论还指出:
请注意,后备缓冲区有一个Java数组支持内容 缓冲区。建议在此后台缓冲区上执行操作 而不是循环put / get。
这是事实,但你遗漏了数组支持缓冲区的全部内容。 阵列支持的缓冲区在数组之上是facade pattern。支持数组的缓冲区永远不会比数组本身更快,因为内部它们必须使用数组。
因此,它们是为了方便而不是速度。换句话说,如果你需要速度,建议选择array over array-facade。如果您需要方便/可读性,建议在阵列上选择array-facade over array以进行类似缓冲区的操作。
同时阅读: