java - type - Cuándo usar Array, Buffer o Direct Buffer
the provided value is not of type ''( arraybuffer or arraybufferview (2)
Pregunta
Mientras escribía una clase de Matrix para usar con las bibliotecas de OpenGL, me encontré con la pregunta de si usar arreglos de Java o una estrategia de Buffer para almacenar los datos (JOGL ofrece una copia de buffer directo para operaciones de Matrix). Para analizar esto, escribí un pequeño programa de prueba de rendimiento que compara las velocidades relativas de las operaciones en bucle y en masa en Arrays vs Buffers versus direct Buffers.
Me gustaría compartir mis resultados con ustedes aquí (ya que los encuentro bastante interesantes). Por favor, siéntase libre de comentar y / o señalar cualquier error.
El código se puede ver en pastebin.com/is7UaiMV .
Notas
La matriz de lectura en bucle se implementa como A [i] = B [i] , de lo contrario, el optimizador JIT eliminará completamente ese código. La var real = A [i] parece ser bastante parecida.
En el resultado de muestra para un tamaño de matriz de 10,000, es muy probable que el optimizador JIT haya reemplazado el acceso de matriz en bucle con una implementación similar a System.arraycopy.
No hay un buffer de obtención masiva-> buffer cuando Java implementa A.get (B) como B.put (A) , por lo tanto, los resultados serían los mismos que los resultados de la venta masiva.
Conclusión
En casi todas las situaciones, se recomienda encarecidamente utilizar las matrices internas de Java. No solo la velocidad de colocación / obtención es mucho más rápida, el JIT también es capaz de realizar optimizaciones mucho mejores en el código final.
Los tampones solo deben usarse si se cumple lo siguiente:
- Necesitas procesar grandes cantidades de datos.
- Esos datos se procesan en su mayoría o siempre en masa .
Tenga en cuenta que un búfer backened tiene una matriz Java que respalda el contenido del búfer. Se recomienda realizar operaciones en este búfer inverso en lugar de poner / obtener bucles.
Los buffers directos solo deben usarse si le preocupa el uso de la memoria y nunca accede a los datos subyacentes. Son un poco más lentos que los buffers no directos, mucho más lentos si se accede a los datos subyacentes, pero utilizan menos memoria. Además, existe una sobrecarga adicional al convertir datos de no bytes (como los arreglos flotantes) en bytes cuando se utiliza un búfer directo.
Para más detalles ver aquí:
- ¿Por qué solo los ByteBuffers son útiles cuando se utilizan buffers directos?
- La sobrecarga interna en NIO y lo que ralentiza los buffers
Resultados de la muestra
Nota: El porcentaje es solo para facilitar la lectura y no tiene un significado real.
Usando matrices de tamaño 16 con 10,000,000 iteraciones ...
-- 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%
Usando matrices de tamaño 1,000 con 100,000 iteraciones ...
-- 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%
Usando matrices de tamaño 10,000 con 100,000 iteraciones ...
-- 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%
Tldr:
Use buffers directos solo si necesitamos hacer E / S de alta velocidad eficientes.
Si necesitamos operaciones eficientes de alta velocidad sin E / S , la matriz predeterminada es la mejor opción.
Si necesitamos realizar operaciones similares a un búfer en una matriz predeterminada, y podemos permitirnos ser lentos , entonces usar un búfer respaldado por una matriz.
Tsdr:
Sus pruebas no probaron ninguna operación de E / S y, por lo tanto, su conclusión es incorrecta.
Su conclusión dice (énfasis no mío):
Los buffers directos solo deben usarse si le preocupa el uso de la memoria y nunca accede a los datos subyacentes. Son un poco más lentos que los buffers no directos, mucho más lentos si se accede a los datos subyacentes, pero utilizan menos memoria. Además, existe una sobrecarga adicional al convertir datos de no bytes (como los arreglos flotantes) en bytes cuando se utiliza un búfer directo.
Eso está claramente mal. Los buffers directos están destinados a resolver problemas de velocidad , no problemas de memoria. Los buffers directos deben usarse siempre que necesite un acceso de E / S de alto rendimiento. Esto incluye operaciones de archivo / red, etc. Es definitivamente más rápido cuando se usa correctamente y, de hecho, es el más rápido que la API de Java proporciona de forma inmediata.
Al realizar operaciones de archivo / red, hay una sobrecarga adicional al convertir datos de no bytes en bytes. Esto es cierto para todo , no solo para los buffers directos.
Su conclusión también establece:
Tenga en cuenta que un búfer backened tiene una matriz Java que respalda el contenido del búfer. Se recomienda realizar operaciones en este búfer inverso en lugar de poner / obtener bucles.
Esto es cierto, pero le falta el punto completo de los búferes respaldados por matriz. Los buffers con respaldo de matriz son un patrón de fachada en la parte superior de las matrices . Los búferes respaldados por arreglos nunca serán más rápidos que los arreglos en sí mismos, ya que internamente tienen que usar el arreglo.
Como tales, están ahí por conveniencia, no por velocidad. En otras palabras, si necesita velocidad, se recomienda elegir la matriz sobre la fachada de matriz. Si necesita conveniencia / legibilidad, se recomienda elegir matriz-fachada sobre matriz para operaciones de tipo búfer en la matriz.
También lea:
Los buffers directos no están destinados a acelerar el acceso desde el código Java. (Si eso fuera posible, había algún problema con la implementación de la matriz de la JVM).
Estos búferes de bytes sirven para interactuar con otros componentes, ya que puede escribir un búfer de bytes en un ByteChannel
y puede usar búferes directos junto con el código nativo, como las bibliotecas OpenGL que mencionó. Se pretende acelerar estas operaciones entonces. El uso del chip de una tarjeta gráfica para renderizar puede acelerar el funcionamiento general en un grado más que compensar el posible acceso más lento al búfer desde el código Java.
Por cierto, si mide la velocidad de acceso a un búfer de bytes, especialmente los búferes de bytes directos, vale la pena cambiar el orden de bytes al orden de bytes nativo antes de adquirir una vista FloatBuffer
:
FloatBuffer bufferD = ByteBuffer.allocateDirect(SIZE * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();