¿Es posible usar sun.misc.Unsafe para llamar a funciones C sin JNI?

6 minutos de lectura

avatar de usuario
Finn el humano

Una pieza de código C/C++ podría proporcionar un método JNI con una matriz de punteros de función. Pero, ¿hay alguna manera de llamar a la pila las funciones a las que apuntan los punteros de la matriz, directamente desde el interior del código Java (sin usar JNI o ​​similar)? JNI de alguna manera hace algo así, por lo que debe haber una manera. ¿Cómo lo hace JNI? ¿Es a través de sun.misc.Unsafe? Incluso si no lo es, ¿podríamos usar alguna solución no segura para tener en nuestras manos el código JVM que hace eso?

No planeo usar eso comercialmente, por supuesto. Ni siquiera soy un profesional, simplemente disfruto de la codificación y he estado estudiando CUDA últimamente, así que pensé que tal vez podría experimentar mezclando todo, pero la sobrecarga de las llamadas JNI frustraría el propósito de tener un código acelerado por GPU.

  • JNI lo hace al estar integrado en la JVM.

    – usuario253751

    30 de marzo de 2016 a las 1:42

avatar de usuario
apagones

¿JNI es tan lento?

JNI ya se ha optimizado mucho, deberías probarlo primero. Pero efectivamente tiene cierta sobrecarga, ver detalles.

Esta sobrecarga puede ser significativa si una función nativa es simple y se llama con frecuencia. JDK tiene una API privada llamada Nativos críticos para reducir la sobrecarga de las funciones de llamada que no requieren mucha funcionalidad JNI.

Nativos críticos

Un método nativo debe cumplir las siguientes condiciones para convertirse en un método nativo crítico:

  • debe ser estático y no sincronizado;
  • los tipos de argumento deben ser primitivo o arreglos primitivos;
  • la implementación no debe llamar a funciones JNI, es decir, no puede asignar objetos Java ni generar excepciones;
  • no debe funcionar durante mucho tiempo, ya que bloqueará GC mientras corre.

La declaración de un nativo crítico parece un método JNI regular, excepto que

  • Empieza con JavaCritical_ en lugar de Java_;
  • no tiene extra JNIEnv* y jclass argumentos;
  • Las matrices de Java se pasan en dos argumentos: el primero es una longitud de matriz y el segundo es un puntero a datos de matriz sin procesar. Es decir, no hay necesidad de llamar GetArrayElements y amigos, puede usar instantáneamente un puntero de matriz directo.

Por ejemplo, un método JNI

JNIEXPORT jint JNICALL
Java_com_package_MyClass_nativeMethod(JNIEnv* env, jclass klass, jbyteArray array) {
    jboolean isCopy;
    jint length = (*env)->GetArrayLength(env, array);
    jbyte* buf = (*env)->GetByteArrayElements(env, array, &isCopy);
    jint result = process(buf, length);
    (*env)->ReleaseByteArrayElements(env, array, buf, JNI_ABORT);
    return result;    
}

se convertirá en

JNIEXPORT jint JNICALL
JavaCritical_com_package_MyClass_nativeMethod(jint length, jbyte* buf) {
    return process(buf, length);
}

Los nativos críticos solo se admiten en HotSpot JVM a partir de JDK 7. Además, la versión “crítica” solo se llama desde el código compilado. Por lo tanto, necesita una implementación crítica y estándar para que esto funcione correctamente.

Esta característica fue diseñada para uso interno en JDK. No hay ninguna especificación pública o algo así. Probablemente la única documentación que puede encontrar es en los comentarios a JDK-7013347.

Punto de referencia

Este punto de referencia muestra que los nativos críticos pueden ser varias veces más rápidos que los métodos regulares de JNI cuando la carga de trabajo nativa es muy pequeña. Cuanto más largo es el método, menor es la sobrecarga relativa.

Realización de llamadas JNI


PD Hay un trabajo en curso en JDK para implementar Native MethodHandles que servirán como una alternativa más rápida a JNI. Sin embargo, es poco probable que aparezca antes de JDK 10.

  1. http://cr.openjdk.java.net/~jrose/panama/native-call-primitive.html
  2. http://mail.openjdk.java.net/pipermail/panama-dev/2015-December/000225.html

  • ¿Cómo puede saber si su programa utilizará la implementación crítica o la estándar? Acabo de probar un ejemplo similar, donde compilé mi archivo Java con javac y lo ejecuté y usó la versión estándar no crítica.

    – Ivailo Toskov

    5 de mayo de 2017 a las 12:26

  • @IvayloToskov Solo los métodos compilados JIT llaman a Critical Natives. Mientras se interpreta un método, llamará a la implementación estándar.

    – apagones

    5 mayo 2017 a las 17:09

  • ¿Debería estar presente la declaración del método en los archivos .c y .h?

    – Jaspreet

    27 de diciembre de 2018 a las 7:14

  • @Jaspreet No es necesario tenerlo en .h

    – apagones

    27 de diciembre de 2018 a las 7:54

  • ¿Se explotará siempre un jbyteArray en un puntero y un valor de longitud o seguirá siendo un proyecto si se desea? Prefiero tomar un proyecto de trabajo y luego hacer referencia a los punteros para determinar si es una matriz o un valor largo. ¿Hay alguna fuente en la JVM que pueda señalar para explicar mejor este comportamiento?

    – Johnny V.

    23 de abril de 2019 a las 14:55

Vale la pena mencionar aquí que otra JVM popular de código abierto tiene un parecido, documentadopero no es una forma popular de acelerar las llamadas JNI para algunos métodos nativos.

Las llamadas nativas más rápidas a la interfaz nativa de Java (JNI) están disponibles mediante el @FastNative y @CriticalNative anotaciones Estas optimizaciones de tiempo de ejecución de ART incorporadas aceleran las transiciones JNI y reemplazan la notación !bang JNI ahora obsoleta. Las anotaciones no tienen efecto en los métodos no nativos y solo están disponibles para el código del lenguaje Java de la plataforma en bootclasspath (sin actualizaciones de Play Store).

los @FastNative La anotación admite métodos no estáticos. Use esto si un método accede a un proyecto de trabajo como parámetro o valor de retorno.

los @CriticalNative La anotación proporciona una forma aún más rápida de ejecutar métodos nativos, con las siguientes restricciones:

  • Los métodos deben ser estáticos, sin objetos para parámetros, valores devueltos o un this implícito.
  • Solo los tipos primitivos se pasan al método nativo.
  • El método nativo no usa los parámetros JNIEnv y jclass en su definición de función.
  • El método debe estar registrado con RegisterNatives en lugar de confiar en la vinculación dinámica de JNI.

los @FastNative y @CriticalNative las anotaciones deshabilitan la recolección de elementos no utilizados al ejecutar un método nativo. No lo use con métodos de ejecución prolongada, incluidos los métodos generalmente rápidos, pero generalmente ilimitados.

Las pausas en la recolección de elementos no utilizados pueden provocar interbloqueos. No adquiera bloqueos durante una llamada nativa rápida si los bloqueos no se han liberado localmente (es decir, antes de volver al código administrado). Esto no se aplica a las llamadas regulares de JNI, ya que ART considera que la ejecución del código nativo está suspendida.

@FastNative puede mejorar el rendimiento del método nativo hasta 3 veces, y @CriticalNative hasta 5x.

Este documento hace referencia al ahora obsoleto !estallido notación que se utilizó para acelerar algunas llamadas nativas en Dalvik JVM.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad