Sur la base des seules performances, combien de lignes "simples" de Java est la performance équivalente atteinte lors d'un appel JNI?
Ou pour essayer d'exprimer la question d'une manière plus concrète, si une simple opération Java telle que
someIntVar1 = someIntVar2 + someIntVar3;
a reçu un index "CPU work" de 1
, quel serait l'indice (approximatif) "CPU work" typique de la surcharge de l'appel JNI?
Cette question ignore le temps nécessaire à l'exécution du code natif. Dans le langage téléphonique, il s'agit strictement de la partie "chute du drapeau" de l'appel, et non du "taux d'appel".
La raison de poser cette question est d'avoir une "règle de base" pour savoir quand déranger la tentative de codage d'un appel JNI lorsque vous connaissez le coût natif (à partir des tests directs) et le Java coût d'une opération donnée. Cela pourrait vous aider à éviter rapidement les tracas du codage de l'appel JNI pour constater que la surcharge de la légende consommait tout avantage de l'utilisation du code natif.
Certaines personnes se bloquent sur les variations de CPU, RAM etc. Tout cela est pratiquement sans rapport avec la question - je demande le coût relatif aux lignes de Java code. Si le CPU et RAM sont pauvres, ils sont mauvais pour les deux Java et JNI donc les considérations environnementales doivent s'équilibrer)) La version JVM entre également dans la catégorie "non pertinente".
Cette question ne demande pas un timing absolu en nanosecondes, mais plutôt un "effort de travail" de baseball en unités de "lignes de simples Java").
Le test de profileur rapide donne:
classe 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");
}
}
Bibliothèque C:
#include <jni.h>
#include "Main.h"
JNIEXPORT int JNICALL
Java_Main_zero(JNIEnv *env, jobject obj)
{
return 0;
}
Résultats:
Détails du système:
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
Mise à jour: Micro-benchmarks Caliper pour x86 (32/64 bits) et ARMv6 sont les suivants:
classe 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");
}
}
Résultats (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 ==
Résultats (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 =
Résultats (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 ========
Pour résumer un peu les choses, il semble que [~ # ~] jni [~ # ~] appel est à peu près équivalent à 10-25 Java ops sur des opérations typiques ( x86) matériel et Hotspot VM. Sans surprise, sous beaucoup moins optimisé Zero VM, les résultats sont assez différents (3-4 ops).
Merci à @ Giovanni Azua et @ Marko Topolnik pour sa participation et ses conseils.
Je viens donc de tester la "latence" d'un appel JNI vers C sur Windows 8.1, 64 bits, en utilisant l'IDE Eclipse Mars, JDK 1.8.0_74 et VirtualVM profiler 1.3.8 avec le module complémentaire de démarrage de profil.
Configuration: (deux méthodes)
QUELQUE CHOSE () passe des arguments, fait des trucs et retourne des arguments
RIEN () ne passe les mêmes arguments, ne fait rien avec eux et renvoie les mêmes arguments.
(chacun est appelé 270 fois)
Durée totale d'exécution pour QUELQUE CHOSE (): 6523ms
Durée totale d'exécution pour RIEN (): ,102 ms
Ainsi, dans mon cas, les appels JNI sont assez négligeables.
Vous devriez en fait tester vous-même la "latence". La latence est définie en ingénierie comme le temps nécessaire pour envoyer un message de longueur nulle. Dans ce contexte, cela correspondrait à l'écriture du plus petit programme Java qui invoque un do_nothing
fonction C++ vide et calcule la moyenne et la valeur standard du temps écoulé sur 30 mesures (effectuez quelques appels d'échauffement supplémentaires). Vous pourriez être surpris des résultats moyens différents faisant de même pour différentes versions et plates-formes JDK.
Ce n'est qu'ainsi que vous obtiendrez la réponse finale quant à savoir si l'utilisation de JNI est logique pour votre environnement cible.