Co powoduje, że połączenia JNI są wolne?

194

Wiem, że „przekraczanie granic” podczas wykonywania wywołania JNI w Javie jest powolne.

Jednak chcę wiedzieć, co powoduje, że jest wolny? Co robi podstawowa implementacja jvm podczas wykonywania wywołania JNI, które powoduje, że jest tak wolny?

pdeva
źródło
2
(+1) Ładne pytanie. Podczas gdy zajmujemy się tym tematem, chciałbym zachęcić każdego, kto dokonał rzeczywistych testów porównawczych, do opublikowania swoich wyników.
NPE
2
Wywołanie JNI musi przekonwertować przekazane obiekty Java na coś, co C (na przykład) może zrozumieć; to samo z wartością zwracaną. Spora część to konwersja typów i zestawianie stosów wywołań.
Dave Newton,
Dave, rozumiem i słyszałem o tym wcześniej. Ale czym dokładnie jest konwersja? co to jest „coś”? Szukam szczegółów.
pdeva
Użycie bezpośrednich ByteBuffers do przesyłania danych między Javą a C może spowodować stosunkowo niski narzut.
Peter Lawrey,
6
połączenie potrzebuje odpowiedniej ramki stosu C, wypychając wszystkie przydatne rejestry procesora (i usuwając je z powrotem), połączenie wymaga ogrodzenia, a także zapobiega wielu optymalizacjom, takim jak inline. Wątki muszą również opuścić blokadę stosu wykonawczego (na przykład, aby umożliwić blokadom stronniczym działanie w kodzie natywnym), a następnie je odzyskać.
bestsss,

Odpowiedzi:

174

Po pierwsze, warto zauważyć, że przez „powolne” mówimy o czymś, co może zająć dziesiątki nanosekund. W przypadku trywialnych metod natywnych w 2010 r. Mierzyłem połączenia średnio 40 ns na pulpicie Windows i 11 ns na pulpicie Mac. Chyba że wykonujesz wiele połączeń, nie zauważysz.

To powiedziawszy, wywołanie metody rodzimej może być wolniejsze niż normalne wywołanie metody Java. Przyczyny obejmują:

  • Metody rodzime nie będą wstawiane przez JVM. Nie będą też kompilowane na czas dla tej konkretnej maszyny - są już skompilowane.
  • Macierz Java można skopiować w celu uzyskania dostępu w kodzie natywnym, a później skopiować. Koszt może być liniowy w stosunku do rozmiaru tablicy. Zmierzyłem kopiowanie JNI 100 000 macierzy średnio na około 75 mikrosekund na pulpicie Windows i 82 mikrosekund na Macu. Na szczęście dostęp bezpośredni można uzyskać za pośrednictwem GetPrimitiveArrayCritical lub NewDirectByteBuffer .
  • Jeśli metoda zostanie przekazana do obiektu lub będzie musiała wykonać wywołanie zwrotne, wówczas natywna metoda prawdopodobnie będzie wykonywać własne wywołania do JVM. Dostęp do pól, metod i typów Java z natywnego kodu wymaga czegoś podobnego do refleksji. Podpisy są określane w ciągach i wyszukiwane w JVM. Jest to zarówno powolne, jak i podatne na błędy.
  • Ciągi Java są obiektami, mają długość i są zakodowane. Dostęp lub utworzenie łańcucha może wymagać kopii O (n).

Niektóre dodatkowe dyskusje, być może datowane, można znaleźć w „Wydajność platformy Java”: strategie i taktyki ”, 2000, Steve Wilson i Jeff Kesselman, w sekcji„ 9.2: Badanie kosztów JNI ”. To około jedna trzecia drogi w dół tej strony , podana w komentarzu @Philip poniżej.

W artykule dla deweloperów IBM z 2009 r. „Najlepsze praktyki korzystania z macierzystego interfejsu Java” podano sugestie dotyczące unikania pułapek wydajności w JNI.

Andy Thomas
źródło
1
Ta odpowiedź twierdzi, że JVM może wstawiać niektóre natywne kody .
AH
5
Ta odpowiedź zauważa, że ​​niektóre standardowe kody natywne są wbudowane w JVM zamiast w JNI. Powyżej „metody rodzime” odnoszą się do ogólnego przypadku metod natywnych zdefiniowanych przez użytkownika zaimplementowanych za pośrednictwem JNI. Dzięki za wskaźnik do sun.misc.Unsafe.
Andy Thomas,
Nie chciałem twierdzić, że takie podejście można zastosować do każdego połączenia JNI. Ale to nie zaszkodzi wiedzieć, że tam jest jakiś kompromis pomiędzy czystego kodu bajtowego i czystego kodu JNI. Być może wpłynie to na niektóre decyzje projektowe. Być może ten mechanizm zostanie uogólniony w przyszłości.
AH
3
@AH, mylisz się z wewnętrznym / JNI. Oni są zupełnie inni. sun.misc.Unsafei całkiem sporo innych rzeczy takich jak System.currentTimeMillis/nanoTimeJVM obsługuje za pomocą „magii”. Nie są one JNI i nie mają w ogóle odpowiednich plików .c / .h, co wyklucza samą implementację JVM. Nie można zastosować tego podejścia, chyba że piszesz / hakujesz JVM.
bestsss
1
ten dokument java.sun.com ” jest obecnie uszkodzony - oto działający link.
Philip Guin
25

Warto wspomnieć, że nie wszystkie metody Java oznaczone znakiem nativesą „wolne”. Niektóre z nich są nieodłączne, co czyni je niezwykle szybkimi. Aby sprawdzić, które są właściwe, a które nie, możesz poszukać do_intrinsicna vmSymbols.hpp .

Tema
źródło
23

Zasadniczo JVM konstruuje interpretacyjnie parametry C dla każdego wywołania JNI, a kod nie jest zoptymalizowany.

W tym dokumencie jest wiele innych szczegółów

Jeśli jesteś zainteresowany porównaniem JNI do kodu natywnego, ten projekt ma kod do uruchamiania testów porównawczych.

dmck
źródło
2
papier, z którym się łączysz, bardziej przypomina papier porównawczy wydajności niż ten, który opisuje, jak działa JNI wewnętrznie.
pdeva
@pdeva Niestety inne zasoby, które znalazłem, były powiązane z java.sun.com, a łącza nie zostały zaktualizowane od czasu przejęcia Oracle. Szukam więcej szczegółów na temat elementów wewnętrznych JNI.
dmck
13
Artykuł dotyczy Java 1.3 - całkiem dawno. Czy problemy z tamtych czasów nadal dotyczą Java 7?
AH