¿Qué hace que las llamadas JNI sean lentas?

194

Sé que 'cruzar límites' al hacer una llamada JNI en Java es lento.

Sin embargo, quiero saber qué es lo que lo hace lento. ¿Qué hace la implementación jvm subyacente al hacer una llamada JNI que la hace tan lenta?

Pdeva
fuente
2
(+1) Buena pregunta. Mientras estamos en el tema, me gustaría animar a cualquiera que haya hecho puntos de referencia reales a publicar sus hallazgos.
NPE
2
Una llamada JNI necesita convertir los objetos Java pasados ​​a algo que C (por ejemplo) pueda entender; Lo mismo con el valor de retorno. La conversión de tipos y la clasificación de la pila de llamadas son una buena parte de ella.
Dave Newton
Dave, entiendo y he oído hablar de eso antes. Pero, ¿cómo es exactamente la conversión? ¿Qué es ese 'algo'? Estoy buscando detalles
pdeva
El uso directo de ByteBuffers para pasar datos entre Java y C puede generar una sobrecarga relativamente baja.
Peter Lawrey
66
la llamada necesita un marco de pila C adecuado, empujando todos los registros útiles de la CPU (y haciéndolos retroceder), la llamada necesita cerco y también evita muchas optimizaciones como en línea. Además, los subprocesos deben abandonar el bloqueo de la pila de ejecución (por ejemplo, para permitir que funcionen los bloqueos sesgados mientras están en código nativo) y luego recuperarlo.
bestsss

Respuestas:

174

Primero, vale la pena señalar que "lento", estamos hablando de algo que puede llevar decenas de nanosegundos. Para los métodos nativos triviales, en 2010 medí las llamadas a un promedio de 40 ns en mi escritorio de Windows y 11 ns en mi escritorio de Mac. A menos que esté haciendo muchas llamadas, no lo notará.

Dicho esto, llamar a un método nativo puede ser más lento que hacer una llamada a un método Java normal. Las causas incluyen:

  • Los métodos nativos no serán alineados por la JVM. Tampoco serán compilados justo a tiempo para esta máquina específica, ya están compilados.
  • Se puede copiar una matriz de Java para acceder en código nativo y luego volver a copiarla. El costo puede ser lineal en el tamaño de la matriz. Medí la copia JNI de una matriz de 100,000 para promediar aproximadamente 75 microsegundos en mi escritorio de Windows y 82 microsegundos en Mac. Afortunadamente, se puede obtener acceso directo a través de GetPrimitiveArrayCritical o NewDirectByteBuffer .
  • Si el método pasa un objeto, o necesita hacer una devolución de llamada, entonces el método nativo probablemente hará sus propias llamadas a la JVM. Acceder a los campos, métodos y tipos de Java desde el código nativo requiere algo similar a la reflexión. Las firmas se especifican en cadenas y se consultan desde la JVM. Esto es lento y propenso a errores.
  • Las cadenas de Java son objetos, tienen longitud y están codificadas. Acceder o crear una cadena puede requerir una copia O (n).

Se puede encontrar una discusión adicional, posiblemente fechada, en "Java¿ Platform Performance: Strategies and Tactics", 2000, de Steve Wilson y Jeff Kesselman, en la sección "9.2: Examen de los costos de JNI". Es aproximadamente un tercio del camino hacia abajo en esta página , proporcionado en el comentario de @Philip a continuación.

El documento de 2009 de IBM developerWorks "Mejores prácticas para usar la interfaz nativa de Java" proporciona algunas sugerencias para evitar dificultades de rendimiento con JNI.

Andy Thomas
fuente
1
Esta respuesta reclamaciones, que algún código nativo puede ser inline por la JVM.
AH
55
Esa respuesta señala que algunos códigos nativos estándar están integrados en la JVM en lugar de usar JNI. Arriba, "métodos nativos" se refiere al caso general de los métodos nativos definidos por el usuario implementados a través de JNI. Gracias por el puntero a sun.misc.Unsefe.
Andy Thomas
No quería afirmar que este enfoque se puede utilizar para cada llamada JNI. Pero no estará de más saber que no es un término medio entre el código de bytes puro y código JNI puro. Quizás esto afectará algunas decisiones de diseño. Quizás este mecanismo se generalice en el futuro.
AH
3
@AH, confundes intrínseco con JNI. Son bastante diferentes. sun.misc.Unsafey muchas otras cosas como System.currentTimeMillis/nanoTimeson manejadas a través de 'magia' por la JVM. No son JNI y no tienen los archivos .c / .h adecuados, dejando al descubierto el JVM impl. El enfoque no puede seguirse a menos que esté escribiendo / pirateando la JVM.
bestsss
1
" este documento java.sun.com " está actualmente roto, aquí hay un enlace de trabajo.
Philip Guin
25

Vale la pena mencionar que no todos los métodos Java marcados con nativeson "lentos". Algunos de ellos son intrínsecos que los hacen extremadamente rápidos. Para verificar cuáles son intrínsecos y cuáles no, puede buscar do_intrinsicen vmSymbols.hpp .

Tema
fuente
23

Básicamente, la JVM construye interpretativamente los parámetros C para cada llamada JNI y el código no está optimizado.

Hay muchos más detalles descritos en este documento.

Si está interesado en comparar el JNI con el código nativo, este proyecto tiene código para ejecutar puntos de referencia.

dmck
fuente
2
el documento al que se vinculó parece más un documento de referencia de rendimiento que uno que describe cómo funciona internamente JNI.
pdeva
@pdeva Lamentablemente, los otros recursos que encontré estaban vinculados a java.sun.com y los enlaces no se han actualizado desde la adquisición de Oracle. Estoy buscando más detalles sobre las partes internas de JNI.
dmck
13
El documento trata sobre Java 1.3, hace bastante tiempo. ¿Los problemas de esa época todavía se aplican a Java 7?
AH