Digamos que el cuello de botella de mi programa Java son realmente algunos bucles ajustados para calcular un montón de productos punto vectoriales. Sí, he perfilado, sí, es el cuello de botella, sí, es significativo, sí, así es como es el algoritmo, sí, he ejecutado Proguard para optimizar el código de bytes, etc.
El trabajo es, esencialmente, productos punto. Como en, tengo dos float[50]
y necesito calcular la suma de productos por pares. Sé que existen conjuntos de instrucciones de procesador para realizar este tipo de operaciones de forma rápida y masiva, como SSE o MMX.
Sí, probablemente pueda acceder a estos escribiendo código nativo en JNI. La llamada JNI resulta bastante cara.
Sé que no puede garantizar lo que compilará o no compilará un JIT. ¿Alguien ha oído hablar de un código de generación JIT que utilice estas instrucciones? y si es así, ¿hay algo en el código Java que ayude a que sea compilable de esta manera?
Probablemente un "no"; vale la pena preguntar.
fuente
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation
. Necesitará un programa que ejecute el método vectorizable suficientes veces para hacerlo "caliente".Respuestas:
Entonces, básicamente, desea que su código se ejecute más rápido. JNI es la respuesta. Sé que dijiste que no te funcionó, pero déjame mostrarte que estás equivocado.
Aquí está
Dot.java
:y
Dot.h
:Podemos compilar y ejecutar eso con JavaCPP usando este comando:
Con una CPU Intel (R) Core (TM) i7-7700HQ a 2.80GHz, Fedora 30, GCC 9.1.1 y OpenJDK 8 u 11, obtengo este tipo de salida:
O aproximadamente 2,4 veces más rápido. Necesitamos utilizar búferes NIO directos en lugar de matrices, pero HotSpot puede acceder a búferes NIO directos tan rápido como matrices . Por otro lado, desenrollar manualmente el lazo no proporciona un aumento mensurable en el rendimiento, en este caso.
fuente
Para abordar parte del escepticismo expresado por otros aquí, sugiero que cualquiera que quiera probarse a sí mismo oa otros use el siguiente método:
Ejemplo:
El resultado con y sin el indicador (en la computadora portátil Haswell reciente, Oracle JDK 8u60): -XX: + UseSuperWord: 475.073 ± 44.579 ns / op (nanosegundos por operación) -XX: -UseSuperWord: 3376.364 ± 233.211 ns / op
El ensamblaje para el bucle activo es un poco difícil de formatear y pegar aquí, pero aquí hay un fragmento (hsdis.so no puede formatear algunas de las instrucciones vectoriales AVX2, así que ejecuté con -XX: UseAVX = 1): -XX: + UseSuperWord (con '-prof perfasm: intelSyntax = true')
¡Diviértete atacando el castillo?
fuente
En las versiones de HotSpot que comienzan con Java 7u40, el compilador del servidor brinda soporte para la vectorización automática. Según JDK-6340864
Sin embargo, esto parece ser cierto solo para los "bucles simples", al menos por el momento. Por ejemplo, la acumulación de una matriz aún no se puede vectorizar JDK-7192383
fuente
Aquí hay un buen artículo sobre cómo experimentar con Java y las instrucciones SIMD escritas por mi amigo: http://prestodb.rocks/code/simd/
Su resultado general es que puede esperar que JIT use algunas operaciones SSE en 1.8 (y algunas más en 1.9). Aunque no debes esperar mucho y debes tener cuidado.
fuente
Puede escribir el kernel OpenCl para hacer la computación y ejecutarlo desde java http://www.jocl.org/ .
El código se puede ejecutar en CPU y / o GPU y el lenguaje OpenCL también admite tipos de vector, por lo que debería poder aprovechar explícitamente, por ejemplo, las instrucciones SSE3 / 4.
fuente
Eche un vistazo a la comparación de rendimiento entre Java y JNI para una implementación óptima de micro-kernels computacionales . Muestran que el compilador del servidor Java HotSpot VM admite la vectorización automática mediante el paralelismo de nivel de superpalabra, que se limita a casos simples de paralelismo dentro del ciclo. Este artículo también le dará una guía sobre si el tamaño de sus datos es lo suficientemente grande como para justificar la ruta JNI.
fuente
Supongo que escribió esta pregunta antes de descubrir netlib-java ;-) proporciona exactamente la API nativa que necesita, con implementaciones optimizadas para la máquina, y no tiene ningún costo en el límite nativo debido a la fijación de memoria.
fuente
No creo que la mayoría de las máquinas virtuales sean lo suficientemente inteligentes para este tipo de optimizaciones. Para ser justos, la mayoría de las optimizaciones son mucho más simples, como cambiar en lugar de multiplicar cuando se trata de una potencia de dos. El proyecto mono introdujo su propio vector y otros métodos con respaldo nativo para ayudar al rendimiento.
fuente