Was macht JNI-Anrufe langsam?

194

Ich weiß, dass das Überschreiten von Grenzen beim Tätigen eines JNI-Aufrufs in Java langsam ist.

Aber ich will wissen , was ist es , dass es langsam macht? Was macht die zugrunde liegende JVM-Implementierung, wenn ein JNI-Aufruf ausgeführt wird, der ihn so langsam macht?

pdeva
quelle
2
(+1) Schöne Frage. Während wir uns mit diesem Thema befassen, möchte ich jeden, der tatsächliche Benchmarks durchgeführt hat, ermutigen, seine Ergebnisse zu veröffentlichen.
NPE
2
Ein JNI-Aufruf muss die übergebenen Java-Objekte in etwas konvertieren, das C (zum Beispiel) verstehen kann. Gleiches gilt für den Rückgabewert. Typkonvertierung und Call-Stack-Marshalling sind ein guter Teil davon.
Dave Newton
Dave, ich verstehe und habe davon schon einmal gehört. Aber wie genau ist die Konvertierung? Was ist das für ein "Etwas"? Ich suche nach Details.
Pdeva
Die Verwendung von direkten ByteBuffern zum Übertragen von Daten zwischen Java und C kann zu einem relativ geringen Overhead führen.
Peter Lawrey
6
Der Aufruf benötigt einen richtigen C-Stack-Frame, der alle nützlichen CPU-Register schiebt (und zurückspringt), der Aufruf muss eingezäunt werden und verhindert viele Optimierungen wie Inline. Außerdem müssen die Threads die Ausführungsstapelsperre verlassen (z. B. damit voreingenommene Sperren im nativen Code funktionieren) und sie dann zurückerhalten.
Bestsss

Antworten:

174

Zunächst ist anzumerken, dass es sich bei "langsam" um etwas handelt, das mehrere zehn Nanosekunden dauern kann. Für triviale native Methoden habe ich 2010 Anrufe mit durchschnittlich 40 ns auf meinem Windows-Desktop und 11 ns auf meinem Mac-Desktop gemessen. Wenn Sie nicht viele Anrufe tätigen, werden Sie es nicht bemerken.

Das Aufrufen einer nativen Methode kann jedoch langsamer sein als das Aufrufen einer normalen Java-Methode. Ursachen sind:

  • Native Methoden werden von der JVM nicht eingebunden. Sie werden auch nicht just-in-time für diese spezielle Maschine kompiliert - sie sind bereits kompiliert.
  • Ein Java-Array kann für den Zugriff in nativem Code kopiert und später zurückkopiert werden. Die Kosten können in der Größe des Arrays linear sein. Ich habe das JNI- Kopieren eines 100.000-Arrays auf durchschnittlich 75 Mikrosekunden auf meinem Windows-Desktop und 82 Mikrosekunden auf dem Mac gemessen . Glücklicherweise kann der direkte Zugriff über GetPrimitiveArrayCritical oder NewDirectByteBuffer erfolgen .
  • Wenn der Methode ein Objekt übergeben wird oder ein Rückruf erforderlich ist, führt die native Methode wahrscheinlich eigene Aufrufe an die JVM durch. Der Zugriff auf Java-Felder, -Methoden und -Typen über den nativen Code erfordert eine ähnliche Reflexion. Signaturen werden in Zeichenfolgen angegeben und von der JVM abgefragt. Dies ist sowohl langsam als auch fehleranfällig.
  • Java-Strings sind Objekte, haben eine Länge und sind codiert. Für den Zugriff auf oder das Erstellen einer Zeichenfolge ist möglicherweise eine O (n) -Kopie erforderlich.

Einige zusätzliche, möglicherweise datierte Diskussionen finden sich in "Java® Platform Performance: Strategies and Tactics", 2000, von Steve Wilson und Jeff Kesselman, in Abschnitt "9.2: Untersuchung der JNI-Kosten". Es ist ungefähr ein Drittel des Weges auf dieser Seite , wie im Kommentar von @Philip unten angegeben.

Das IBM DeveloperWorks-Dokument 2009 "Best Practices für die Verwendung der Java Native Interface" enthält einige Vorschläge zur Vermeidung von Leistungsproblemen mit JNI.

Andy Thomas
quelle
1
Diese Antwort behauptet, dass ein Teil des nativen Codes von der JVM eingefügt werden kann .
AH
5
In dieser Antwort wird darauf hingewiesen, dass ein nativer Standardcode in der JVM integriert ist, anstatt JNI zu verwenden. Oben bezieht sich "native Methoden" auf den allgemeinen Fall von benutzerdefinierten nativen Methoden, die über JNI implementiert werden. Vielen Dank für den Hinweis auf sun.misc.Unsafe.
Andy Thomas
Ich wollte nicht behaupten, dass dieser Ansatz für jeden JNI-Aufruf verwendet werden kann. Aber es wird weh tun weiß nicht , dass es ist einiger Mittelweg zwischen reinem Bytecode und reinem JNI - Code. Möglicherweise wirkt sich dies auf einige Entwurfsentscheidungen aus. Vielleicht wird dieser Mechanismus in Zukunft verallgemeinert.
AH
3
@AH, Sie verwechseln intrinsisch mit JNI. Sie sind ganz anders. sun.misc.Unsafeund ziemlich viele andere Dinge wie System.currentTimeMillis/nanoTimewerden von der JVM über "Magie" gehandhabt. Sie sind keine JNI und sie haben überhaupt keine richtigen .c / .h-Dateien, was das JVM-Impl selbst entblößt. Der Ansatz kann nur befolgt werden, wenn Sie die JVM schreiben / hacken.
Bests am
1
" this java.sun.com document " ist derzeit defekt - hier ist ein funktionierender Link.
Philip Guin
25

Erwähnenswert ist, dass nicht alle mit gekennzeichneten Java-Methoden native"langsam" sind. Einige von ihnen sind intrinsisch , was sie extrem schnell macht. Um zu überprüfen, welche intrinsisch sind und welche nicht, können Sie do_intrinsicunter vmSymbols.hpp suchen .

Tema
quelle
23

Grundsätzlich konstruiert die JVM die C-Parameter für jeden JNI-Aufruf interpretativ und der Code ist nicht optimiert.

In diesem Dokument werden viele weitere Details beschrieben

Wenn Sie daran interessiert sind, JNI mit nativem Code zu vergleichen, enthält dieses Projekt Code zum Ausführen von Benchmarks.

dmck
quelle
2
Das Papier, mit dem Sie verlinkt haben, scheint eher ein Leistungsbenchmarkpapier zu sein als ein Papier, das beschreibt, wie JNI intern funktioniert.
Pdeva
@pdeva Leider waren die anderen Ressourcen, die ich gefunden habe, mit java.sun.com verknüpft und die Links wurden seit der Übernahme von Oracle nicht aktualisiert. Ich suche nach weiteren Details zu den JNI-Interna.
dmck
13
Das Papier handelt von Java 1.3 - vor ziemlich langer Zeit. Treffen die damaligen Probleme immer noch auf Java 7 zu?
AH