java c optimization jni

java - math.exp python



Math.exp() más rápido a través de JNI? (15)

Ejecuto un algoritmo de ajuste y el error mínimo del resultado de ajuste es mucho mayor que la precisión de Math.exp ().

Las funciones trascendentales son siempre mucho más lentas que la suma o multiplicación y un cuello de botella bien conocido. Si sabe que sus valores están en un rango estrecho, puede simplemente crear una tabla de búsqueda (dos ordenadas ordenadas, una entrada, una salida). Utilice Arrays.binarySearch para buscar el índice correcto e interpolar el valor con los elementos en [índice] e [índice + 1].

Otro método es dividir el número. Tomemos por ejemplo 3.81 y dividamos eso en 3 + 0.81. Ahora multiplicas e = 2.718 tres veces y obtienes 20.08.

Ahora a 0.81. Todos los valores entre 0 y 1 convergen rápidamente con la conocida serie exponencial

1 + x + x ^ 2/2 + x ^ 3/6 + x ^ 4/24 ... etc.

Tómese los términos que necesite para precisión; desafortunadamente es más lento si x se aproxima a 1. Digamos que vas a x ^ 4, luego obtienes 2.2445 en lugar de 2.2448

Luego, multiplique el resultado 2.781 ^ 3 = 20.08 con 2.781 ^ 0.81 = 2.2445 y obtendrá el resultado 45.07 con un error de una parte de dos mil (correcto: 45.15).

Necesito calcular Math.exp() desde Java con mucha frecuencia, ¿es posible obtener una versión nativa para ejecutar más rápido que Math.exp() de java ?

Intenté simplemente jni + C, pero es más lento que simplemente Java .


+1 a escribir su propia implementación de exp (). Es decir, si esto es realmente un cuello de botella en su aplicación. Si puede tratar con un poco de inexactitud, existen varios algoritmos de estimación de exponente extremadamente eficientes, algunos de ellos datan de siglos atrás. Según entiendo, la implementación exp () de Java es bastante lenta, incluso para algoritmos que deben devolver resultados "exactos".

Ah, y no tengas miedo de escribir esa implementación de exp () en Java puro. JNI tiene una gran cantidad de sobrecarga, y la JVM puede optimizar bytecode en tiempo de ejecución a veces incluso más allá de lo que C / C ++ puede lograr.


Dado que el código de Java se compilará en el código nativo con el compilador just-in-time (JIT), realmente no hay ninguna razón para usar JNI para llamar al código nativo.

Además, no debe almacenar en caché los resultados de un método donde los parámetros de entrada son números reales de coma flotante. Las ganancias obtenidas en el tiempo se perderán mucho en la cantidad de espacio utilizado.


El problema con el uso de JNI es la sobrecarga implicada en hacer la llamada a JNI. La máquina virtual Java está bastante optimizada en estos días, y las llamadas al Math.exp incorporado () se optimizan automáticamente para llamar directamente a la función C exp (), e incluso se pueden optimizar en ensamblaje de punto flotante x87 directo instrucciones.


Es posible que pueda hacer que funcione más rápido si lo hace en lotes. Hacer una llamada JNI agrega sobrecarga, por lo que no desea hacerlo por cada exp () que necesite calcular. Trataría de pasar una matriz de 100 valores y obtener los resultados para ver si ayuda al rendimiento.


Escribe el tuyo, adaptado a tus necesidades.

Por ejemplo, si todos tus exponentes tienen el poder de dos, puedes usar el cambio de bit. Si trabaja con un rango o conjunto de valores limitado, puede usar tablas de búsqueda. Si no necesita precisión de punta de alfiler, usa un algoritmo impreciso pero más rápido.


Hay algoritmos más rápidos para exp dependiendo de lo que estás tratando de lograr. ¿El espacio del problema está restringido a un cierto rango, solo necesita una cierta resolución, precisión o precisión, etc.

Si define su problema muy bien, puede encontrar que puede usar una tabla con interpolación, por ejemplo, que eliminará casi cualquier otro algoritmo del agua.

¿Qué restricciones puede aplicar a exp para obtener esa compensación de rendimiento?

-Adán


Hay un costo asociado con llamar a través del límite de JNI.

Si pudiera mover el bucle que llama a exp () en el código nativo también, de modo que solo haya una llamada nativa, entonces podría obtener mejores resultados, pero dudo que sea mucho más rápido que la solución Java pura.

No conozco los detalles de su aplicación, pero si tiene un conjunto bastante limitado de posibles argumentos para la llamada, puede usar una tabla de búsqueda pre calculada para agilizar su código de Java.


La verdadera pregunta es: ¿se ha convertido esto en un cuello de botella para ti? ¿Ha perfilado su aplicación y descubrió que esta es una causa importante de desaceleración?

Si no, recomendaría usar la versión de Java. Intente no preoptimizar ya que esto solo provocará que el desarrollo se ralentice. Puede gastar una cantidad de tiempo extendida en un problema que puede no ser un problema.

Dicho esto, creo que su prueba le dio su respuesta. Si jni + C es más lento, usa la versión de java.



También querrás envolver cualquier bucle que Math.exp() en C. De lo contrario, la sobrecarga de la clasificación entre Java y C saturará cualquier ventaja de rendimiento.


Usa Java''s

Además, guarde los resultados de la caché y luego puede buscar la respuesta más rápido que calcularlos de nuevo.


Puede que ya no sea relevante, pero para que lo sepas, en los lanzamientos más recientes de OpenJDK (mira aquí ), Math.exp debería convertirse en algo intrínseco (si no sabes qué es eso, mira aquí ).

Esto hará que el rendimiento sea inmejorable en la mayoría de las arquitecturas, ya que significa que la máquina virtual Hotspot reemplazará la llamada a Math.exp por una implementación de exp específica del procesador en tiempo de ejecución. Nunca puede superar estas llamadas, ya que están optimizadas para la arquitectura ...


Commons Math3 se envía con una versión optimizada: FastMath.exp(double x) . Aumentó mi código significativamente.

Fabien realizó algunas pruebas y descubrió que era casi dos veces más rápido que Math.exp() :

0.75s for Math.exp sum=1.7182816693332244E7 0.40s for FastMath.exp sum=1.7182816693332244E7

Aquí está el javadoc:

Calcula exp (x), el resultado de la función es casi redondeado. Se redondeará correctamente al valor teórico para el 99.9% de los valores de entrada, de lo contrario tendrá un error de 1 UPL.

Método:

Lookup intVal = exp(int(x)) Lookup fracVal = exp(int(x-int(x) / 1024.0) * 1024.0 ); Compute z as the exponential of the remaining bits by a polynomial minus one exp(x) = intVal * fracVal * (1 + z)

Precisión: el cálculo se realiza con 63 bits de precisión, por lo que el resultado se debe redondear correctamente para el 99,9% de los valores de entrada, con un error de menos de 1 ULP.


Esto ya se ha solicitado varias veces (ver, por ejemplo, aquí ). Aquí hay una aproximación a Math.exp (), copiada de esta publicación del blog :

public static double exp(double val) { final long tmp = (long) (1512775 * val + (1072693248 - 60801)); return Double.longBitsToDouble(tmp << 32); }

Es básicamente lo mismo que una tabla de búsqueda con 2048 entradas e interpolación lineal entre las entradas, pero todo esto con trucos de punto flotante IEEE. Es 5 veces más rápido que Math.exp () en mi máquina, pero esto puede variar drásticamente si compila con -server.