Qu'est-ce qui ralentit les appels JNI?

194

Je sais que «franchir les frontières» lors d'un appel JNI en Java est lent.

Cependant je veux savoir ce qui le ralentit? Que fait l'implémentation jvm sous-jacente lors d'un appel JNI qui le rend si lent?

pdeva
la source
2
(+1) Belle question. Pendant que nous sommes sur le sujet, j'aimerais encourager tous ceux qui ont fait de véritables repères à publier leurs conclusions.
NPE
2
Un appel JNI doit convertir les objets Java transmis en quelque chose que C (par exemple) peut comprendre; même chose avec la valeur de retour. La conversion de type et le marshaling de pile d'appels en sont une bonne partie.
Dave Newton
Dave, je comprends et j'en ai déjà entendu parler. Mais à quoi ressemble exactement la conversion? quel est ce «quelque chose»? Je cherche des détails.
pdeva
L'utilisation directe de ByteBuffers pour transmettre des données entre Java et C peut entraîner une surcharge relativement faible.
Peter Lawrey
6
l'appel a besoin d'un cadre de pile C approprié, poussant tous les registres utiles du processeur (et les renvoyant), l'appel a besoin d'une clôture et empêche également de nombreuses optimisations comme en ligne. Les threads doivent également quitter le verrou de la pile d'exécution (par exemple pour permettre aux verrous biaisés de fonctionner en code natif), puis le récupérer.
bestsss

Réponses:

174

Tout d'abord, il convient de noter que par «lent», nous parlons de quelque chose qui peut prendre des dizaines de nanosecondes. Pour les méthodes natives triviales, en 2010, j'ai mesuré les appels à une moyenne de 40 ns sur mon bureau Windows et 11 ns sur mon bureau Mac. À moins de passer de nombreux appels, vous ne le remarquerez pas.

Cela dit, appeler une méthode native peut être plus lent que faire un appel de méthode Java normal. Les causes incluent:

  • Les méthodes natives ne seront pas intégrées par la JVM. Ils ne seront pas non plus compilés juste à temps pour cette machine spécifique - ils sont déjà compilés.
  • Un tableau Java peut être copié pour y accéder en code natif, puis recopié ultérieurement. Le coût peut être linéaire dans la taille du tableau. J'ai mesuré la copie JNI d'un tableau de 100000 en moyenne environ 75 microsecondes sur mon bureau Windows et 82 microsecondes sur Mac. Heureusement, un accès direct peut être obtenu via GetPrimitiveArrayCritical ou NewDirectByteBuffer .
  • Si la méthode reçoit un objet ou doit effectuer un rappel, la méthode native effectuera probablement ses propres appels à la JVM. L'accès aux champs, méthodes et types Java à partir du code natif nécessite quelque chose de similaire à la réflexion. Les signatures sont spécifiées dans des chaînes et interrogées à partir de la JVM. Ceci est à la fois lent et sujet aux erreurs.
  • Les chaînes Java sont des objets, ont une longueur et sont codées. L'accès ou la création d'une chaîne peut nécessiter une copie O (n).

Une discussion supplémentaire, peut-être datée, peut être trouvée dans "Java¿ Platform Performance: Strategies and Tactics", 2000, par Steve Wilson et Jeff Kesselman, dans la section "9.2: Examiner les coûts JNI". C'est environ un tiers de la descente de cette page , fourni dans le commentaire de @Philip ci-dessous.

Le document IBM developerWorks 2009 "Meilleures pratiques pour l'utilisation de l'interface native Java" fournit quelques suggestions pour éviter les pièges de performances avec JNI.

Andy Thomas
la source
1
Cette réponse prétend que du code natif peut être incorporé par la JVM.
AH
5
Cette réponse note que du code natif standard est intégré dans la JVM au lieu d'utiliser JNI. Ci-dessus, «méthodes natives» fait référence au cas général des méthodes natives définies par l'utilisateur implémentées via JNI. Merci pour le pointeur vers sun.misc.Unsafe.
Andy Thomas
Je ne voulais pas prétendre que cette approche peut être utilisée pour chaque appel JNI. Mais il ne fera pas mal de savoir qu'il ya est un moyen terme entre bytecode pur et pur code JNI. Cela affectera peut-être certaines décisions de conception. Peut-être que ce mécanisme est généralisé à l'avenir.
AH
3
@AH, vous confondez intrinsèque avec JNI. Ils sont assez différents. sun.misc.Unsafeet beaucoup d'autres choses comme System.currentTimeMillis/nanoTimesont gérées via «magie» par la JVM. Ce ne sont pas des JNI et ils n'ont pas du tout de fichiers .c / .h corrects, à l'exception de l'implément JVM lui-même. L'approche ne peut être suivie que si vous écrivez / piratez la JVM.
bestsss
1
" ce document java.sun.com " est actuellement cassé - voici un lien fonctionnel.
Philip Guin
25

Il est à noter que toutes les méthodes Java marquées d'un ne nativesont pas "lentes". Certains d'entre eux sont intrinsèques qui les rendent extrêmement rapides. Pour vérifier lesquels sont intrinsèques et lesquels ne le sont pas, vous pouvez rechercher do_intrinsicsur vmSymbols.hpp .

Tema
la source
23

Fondamentalement, la JVM construit de manière interprétative les paramètres C pour chaque appel JNI et le code n'est pas optimisé.

Il y a beaucoup plus de détails décrits dans cet article

Si vous êtes intéressé par l'analyse comparative JNI par rapport au code natif, ce projet a du code pour exécuter des tests de performance.

dmck
la source
2
le document auquel vous avez lié ressemble plus à un document de référence de performance qu'à un document décrivant le fonctionnement interne de JNI.
pdeva
@pdeva Malheureusement, les autres ressources que j'ai trouvées étaient liées à java.sun.com et les liens n'ont pas été mis à jour depuis l'acquisition d'Oracle. Je recherche plus de détails sur les internes JNI.
dmck
13
Le papier parle de Java 1.3 - il y a assez longtemps. Les problèmes de l'époque s'appliquent-ils toujours à Java 7?
AH