Quel est le surcoût quantitatif associé à un appel JNI?

En se basant uniquement sur les performances, combien de lignes “simples” de java sont-elles le résultat de la performance d’un appel JNI?

Ou pour essayer d’exprimer la question de manière plus concrète, si une simple opération Java telle que

someIntVar1 = someIntVar2 + someIntVar3; 

a été donné un index “CPU work” de 1 , quel serait l’index “CPU work” typique de la surcharge de traitement de l’appel JNI?

Cette question ignore le temps d’attente du code natif pour s’exécuter. Dans le langage téléphonique, il s’agit ssortingctement de la partie “coup de drapeau” de l’appel, et non du “taux d’appel”.

La raison de cette question est d’avoir une “règle générale” pour savoir quand essayer de coder un appel JNI lorsque vous connaissez le coût natif (du test direct) et le coût Java d’une opération donnée. Cela pourrait vous aider à éviter rapidement le codage de l’appel JNI, mais à découvrir que l’utilisation du code natif a été bénéfique pour le traitement des appels.

Modifier:

Certaines personnes sont coincées sur les variations de CPU, de RAM, etc. Elles sont toutes pratiquement sans rapport avec la question: je demande le coût relatif des lignes de code Java. Si le CPU et la RAM sont médiocres, ils sont médiocres pour Java et JNI. Les considérations environnementales doivent donc être équilibrées. La version de 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” en unités de “lignes de code Java simple”.

Test de profileur rapide:

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(Ssortingng[] args) { testNative(); test(); } static { System.loadLibrary("foo"); } } 

Bibliothèque C:

 #include  #include "Main.h" JNIEXPORT int JNICALL Java_Main_zero(JNIEnv *env, jobject obj) { return 0; } 

Résultats:

invocation unique10 appels en boucle100 appels en boucle

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: Les 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 sortingals Scenario{benchmark=AddIntOperation} 0.47 ns; σ=0.02 ns @ 10 sortingals Scenario{benchmark=AddLongOperation} 0.92 ns; σ=0.02 ns @ 10 sortingals 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 sortingals Scenario{benchmark=AddIntOperation} 0.29 ns; σ=0.00 ns @ 3 sortingals Scenario{benchmark=AddLongOperation} 0.26 ns; σ=0.00 ns @ 3 sortingals 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 sortingals Scenario{benchmark=AddIntOperation} 183.46 ns; σ=0.54 ns @ 3 sortingals Scenario{benchmark=AddLongOperation} 199.36 ns; σ=0.65 ns @ 3 sortingals benchmark ns linear runtime JniCall 679 ============================== AddIntOperation 183 ======== AddLongOperation 199 ======== 

Pour résumer un peu les choses, il semble que l'appel JNI équivaut à peu près à 10-25 opérations Java sur du matériel type ( x86 ) et sur une machine virtuelle Hotspot . Sans surprise, sous Zero VM beaucoup moins optimisé, les résultats sont assez différents (3-4 opérations).


Merci à @ Giovanni Azua et @ Marko Topolnik pour leur participation et leurs conseils.

Je viens donc de tester la “latence” d’un appel JNI vers C sous Windows 8.1, 64 bits, en utilisant l’IDE Eclipse Mars, le JDK 1.8.0_74 et le profileur VirtualVM 1.3.8 avec le module complémentaire Profile Startup.

Setup: (deux méthodes)
SOMETHING () transmet des arguments, effectue des tâches et renvoie des arguments
NOTHING () passe dans les mêmes arguments, ne fait rien avec eux et renvoie les mêmes arguments.

(chacun s’appelle 270 fois)
Durée totale de SOMETHING (): 6523ms
Durée d’ exécution totale pour NOTHING (): 0,102 ms

Ainsi, dans mon cas, les appels JNI sont assez négligeables.

Vous devriez réellement tester vous-même ce qu’est 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, il s’agirait d’écrire le plus petit programme Java qui invoque une do_nothing C ++ vide de do_nothing et de calculer la moyenne et le stddev du temps écoulé sur 30 mesures (faire quelques appels de préchauffage supplémentaires). Vous pourriez être surpris par les différents résultats moyens obtenus pour les différentes versions et plates-formes JDK.

Ce faisant, vous obtiendrez la réponse définitive à la question de savoir si JNI est adapté à votre environnement cible.