java - tipos - ¿Cuál es la sobrecarga cuantitativa de hacer una llamada JNI?
sobrecarga de trabajo (3)
Así que acabo de probar la "latencia" para una llamada JNI a C en Windows 8.1, de 64 bits, usando el Eclipse Mars IDE, JDK 1.8.0_74 y VirtualVM profiler 1.3.8 con el complemento Profile Startup.
Configuración: (dos métodos)
ALGO () pasa argumentos, hace cosas y devuelve argumentos
NADA () pasa los mismos argumentos, no hace nada con ellos y devuelve los mismos argumentos.
(cada uno recibe una llamada 270 veces)
Tiempo total de ejecución para ALGO (): 6523ms
Tiempo total de ejecución para NADA (): 0.102ms
Por lo tanto, en mi caso las llamadas JNI son bastante insignificantes.
Basado solo en el rendimiento, ¿aproximadamente cuántas líneas "simples" de java es el golpe de rendimiento equivalente de hacer una llamada JNI?
O para tratar de expresar la pregunta de una manera más concreta, si una operación Java simple como
someIntVar1 = someIntVar2 + someIntVar3;
se le dio un índice de "trabajo de la CPU" de 1
, ¿cuál sería el índice típico de "CPU de trabajo" de la sobrecarga de hacer la llamada JNI?
Esta pregunta ignora el tiempo que lleva esperar a que se ejecute el código nativo. En el habla telefónica, se trata estrictamente de la parte de "caída de la bandera" de la llamada, no de la "tasa de llamada".
La razón para hacer esta pregunta es tener una "regla general" para saber cuándo molestarse en intentar codificar una llamada JNI cuando conoces el costo nativo (de las pruebas directas) y el costo de java de una operación determinada. Podría ayudarlo a evitar rápidamente la molestia de codificar la llamada JNI solo para descubrir que la sobrecarga del letrero consumió todos los beneficios del uso del código nativo.
Editar:
Algunas personas se están colgando de las variaciones en la CPU, la RAM, etc. Todos son prácticamente irrelevantes para la pregunta: estoy preguntando por el costo relativo de las líneas de código Java. Si la CPU y la RAM son deficientes, son malas tanto para Java como para JNI, por lo que las consideraciones medioambientales deberían equilibrarse. La versión de JVM cae también en la categoría "irrelevante".
Esta pregunta no está pidiendo un tiempo absoluto en nanosegundos, sino más bien un "esfuerzo de trabajo" de parque de pelota en unidades de "líneas de código Java simple".
En realidad, debería probarlo usted mismo qué es la "latencia". La latencia se define en ingeniería como el tiempo que lleva enviar un mensaje de longitud cero. En este contexto, correspondería escribir el programa Java más pequeño que invoque una do_nothing
vacía de C ++ sin calcular y calcule la media y el stddev del tiempo transcurrido en 30 mediciones (haga un par de llamadas de calentamiento adicionales). Es posible que se sorprenda de los diferentes resultados promedio que hacen lo mismo para diferentes versiones y plataformas JDK.
Solo hacerlo le dará la respuesta final de si el uso de JNI tiene sentido para su entorno objetivo.
Rendimiento rápido de la prueba de perfil:
Clase de Java:
public class Main {
private static native int zero();
private static int testNative() {
return Main.zero();
}
private static int test() {
return 0;
}
public static void main(String[] args) {
testNative();
test();
}
static {
System.loadLibrary("foo");
}
}
Biblioteca C:
#include <jni.h>
#include "Main.h"
JNIEXPORT int JNICALL
Java_Main_zero(JNIEnv *env, jobject obj)
{
return 0;
}
Resultados:
Detalles del sistema:
java version "1.7.0_09"
OpenJDK Runtime Environment (IcedTea7 2.3.3) (7u9-2.3.3-1)
OpenJDK Server VM (build 23.2-b09, mixed mode)
Linux visor 3.2.0-4-686-pae #1 SMP Debian 3.2.32-1 i686 GNU/Linux
Actualización: los micro-benchmarks de Caliper para x86 (32/64 bit) y ARMv6 son los siguientes:
Clase de Java:
public class Main extends SimpleBenchmark {
private static native int zero();
private Random random;
private int[] primes;
public int timeJniCall(int reps) {
int r = 0;
for (int i = 0; i < reps; i++) r += Main.zero();
return r;
}
public int timeAddIntOperation(int reps) {
int p = primes[random.nextInt(1) + 54]; // >= 257
for (int i = 0; i < reps; i++) p += i;
return p;
}
public long timeAddLongOperation(int reps) {
long p = primes[random.nextInt(3) + 54]; // >= 257
long inc = primes[random.nextInt(3) + 4]; // >= 11
for (int i = 0; i < reps; i++) p += inc;
return p;
}
@Override
protected void setUp() throws Exception {
random = new Random();
primes = getPrimes(1000);
}
public static void main(String[] args) {
Runner.main(Main.class, args);
}
public static int[] getPrimes(int limit) {
// returns array of primes under $limit, off-topic here
}
static {
System.loadLibrary("foo");
}
}
Resultados (x86 / i7500 / Hotspot / Linux):
Scenario{benchmark=JniCall} 11.34 ns; σ=0.02 ns @ 3 trials
Scenario{benchmark=AddIntOperation} 0.47 ns; σ=0.02 ns @ 10 trials
Scenario{benchmark=AddLongOperation} 0.92 ns; σ=0.02 ns @ 10 trials
benchmark ns linear runtime
JniCall 11.335 ==============================
AddIntOperation 0.466 =
AddLongOperation 0.921 ==
Resultados (amd64 / phenom 960T / Hostspot / Linux):
Scenario{benchmark=JniCall} 6.66 ns; σ=0.22 ns @ 10 trials
Scenario{benchmark=AddIntOperation} 0.29 ns; σ=0.00 ns @ 3 trials
Scenario{benchmark=AddLongOperation} 0.26 ns; σ=0.00 ns @ 3 trials
benchmark ns linear runtime
JniCall 6.657 ==============================
AddIntOperation 0.291 =
AddLongOperation 0.259 =
Resultados (armv6 / BCM2708 / Zero / Linux):
Scenario{benchmark=JniCall} 678.59 ns; σ=1.44 ns @ 3 trials
Scenario{benchmark=AddIntOperation} 183.46 ns; σ=0.54 ns @ 3 trials
Scenario{benchmark=AddLongOperation} 199.36 ns; σ=0.65 ns @ 3 trials
benchmark ns linear runtime
JniCall 679 ==============================
AddIntOperation 183 ========
AddLongOperation 199 ========
Para resumir un poco las cosas, parece que la llamada JNI es más o menos equivalente a 10-25 java ops en hardware típico ( x86 ) y VM de Hotspot . No es de extrañar, bajo Zero VM mucho menos optimizado, los resultados son bastante diferentes (3-4 operaciones).
Gracias a @ Giovanni Azua y @ Marko Topolnik por su participación y sugerencias.