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?


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.


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 .


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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.