¿Qué hace que las llamadas JNI sean lentas?

Sé que “cruzar fronteras” cuando se hace una llamada JNI en Java es lento.

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

En primer lugar, vale la pena señalar que por “lento”, estamos hablando de algo que puede tomar 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 el escritorio de mi Mac. A menos que esté haciendo muchas llamadas, no se dará cuenta.

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

  • Los métodos nativos no serán subrayados por la JVM. Tampoco serán comstackdos justo a tiempo para esta máquina específica, ya están comstackdos.
  • Una matriz de Java se puede copiar para acceder en código nativo, y luego copiar de nuevo. El costo puede ser lineal en el tamaño de la matriz. Medí la copia de JNI de una matriz de 100.000 para promediar unos 75 microsegundos en mi escritorio de Windows y 82 microsegundos en Mac. Afortunadamente, el acceso directo se puede obtener a través de GetPrimitiveArrayCritical o NewDirectByteBuffer .
  • Si el método se pasa un objeto, o necesita hacer una callback, entonces el método nativo probablemente hará sus propias llamadas a la JVM. El acceso 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 codificados. Acceder o crear una cadena puede requerir una copia de O (n).

Se puede encontrar alguna discusión adicional, posiblemente fechada, en “Rendimiento de la plataforma Java: Estrategias y tácticas”, 2000, por 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 , provisto en el comentario de @Philip a continuación.

El documento de IBM developerWorks de 2009, “Mejores prácticas para el uso de la interfaz nativa de Java”, proporciona algunas sugerencias para evitar riesgos de rendimiento con JNI.

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 la comparación de JNI vs código nativo, este proyecto tiene código para ejecutar benchmarks.

Vale la pena mencionar que no todos los métodos de Java marcados con native son “lentos”. Algunos de ellos son intrínsecos y los hacen extremadamente rápidos. Para comprobar cuáles son intrínsecos y cuáles no, puede buscar do_intrinsic en vmSymbols.hpp .