Implementacja wywoływania metod z użyciem refleksji ma całkiem bogatą historię. Dostępna od początku w Javie (początkowo bardzo niewydajna), gruntownie przepisana w Javie 1.4, następnie zyskała konkurencję w postaci MethodHandle
. Ostatecznie konkurenci będą musieli się pogodzić, gdyż w Jdk 18 w ramach JEP-416 wywołanie metod z użyciem refleksji tj. Method::invoke
zostanie przepisane na używające pod spodem MethodHandle
.
Co aktualnie nie domaga w refleksji?
Zacznijmy od tego, jak aktualnie działa wywołanie Method::invoke
.
- Sprawdzany jest dostęp do tej metody – czy w ogóle możemy ją wywołać.
- Jeśli ta metoda była wywoływana często, to ostatecznie treść wywoływanej metody jest kompilowana
JIT
em. Trzeba zatem sprawdzić, czy taki kod istnieje i go wywołać. - Jeśli metoda nie jest skompilowana
JIT
em, to wywoływana jest metoda natywna uruchamiająca kod metody.
Warto zauważyć, że dostęp jest sprawdzany przy każdym wywołaniu. Jest to nadmiarowe, gdyż wystarczyłoby sprawdzić kontekst na poziomie klasy tylko raz i taki kontekst wywoływania zcache’ować w klasie.
Drugą niedogodnością jest brak możliwości wniknięcia JIT
a do treści wywoływanej metody. JIT
po prostu nie wie, jakie operacje są wywoływane w tej metodzie – traktuje wywołaną metodę jako „black box”.
Method Handle
Te dwie wady Method::invoke
adresuje mechanizm MethodHandle
wprowadzony w Jdk 1.7. Po pierwsze kontekst (Lookup
) jest wymagany do znalezienia uchwytu na metodę, zatem dostęp sprawdzany jest jednokrotnie.
Po drugie MethodHandle
rozumie kod wykonywany, dzięki czemu JIT
ma możliwość zinline’owania wykonywanego kodu.
O ile ten mechanizm pozwala na wykonanie optymalnego kodu, o tyle problemem jest poziom trudności. Znacznie trudniej stworzyć kod wywołujący MethodHandle
i łatwiej w nim o pomyłkę. Zrozumienie takiego kodu również jest trudniejsze.
Dodatkowo, o ile JIT
ma możliwość rozumienia treści MethodHandle
, o tyle ta informacja jest wykorzystywana praktycznie tylko w przypadku MethodHandle
przetrzymywanych w polach statycznych finalnych (constant
). W innych sytuacjach JIT
nie wykorzystuje informacji o wykonywanym kodzie (a przynajmniej OpenJdk tak nie robi).
JEP-416
Próbę pożenienia Method::invoke
z MethodHandle
podejmuje wspomniany JEP-416. Dostarczony zostanie wraz z Jdk 18 w najbliższych dniach.
Zgodnie z oczekiwaniami otrzymujemy usprawnienia działania z MethodHandle
wraz z prostotą wykonania Method::invoke
. Dodatkowo autorzy JEPa wspominają jeszcze o zalecie „mniejszej ilości StackFrame
’ów” oraz ułatwieniu dalszego rozwoju platformy poprzez usunięciu specjalnego traktowania refleksji.
Wydajność
Normalnie w tym akapicie przeszedłbym do benchmarkowania rozwiązania, gdyby nie to, że wyniki benchmarków są już zawarte w JEPie. O ile znacząca poprawa wydajności w przypadku Method
, Constructor
i Field
trzymanych w polach static final
(43–57% poprawa) zadowala, o tyle zawieść mogą benchmarki innych wywołań (51–77% pogorszenie).
Autorzy zapewniają, że w rzeczywistych aplikacjach zmiany nie mają znaczenia (a sprawdzili w Jacksonie, XStream i Kryo). Obiecują również poprawę na polach na których wystąpiło pogorszenie.
Podsumowanie
Wprowadzone zmiany należy mimo wszystko uznać za pozytywne. O ile zmniejszenie wydajności ma negatywny wydźwięk, o tyle zwiększenie utrzymywalności, potencjał na zwiększenie wydajności oraz uzasadnienie przygotowaniem pod Valhallę i Loom kompensują ten negatywny efekt.
Żebyśmy jeszcze dożyli tychże…
Jeśli ten wpis Cię zainteresował, polecam moje dwa poprzednie wpisy o refleksji – Taka refleksja tylko szybsza… i Przepisujemy Jacksona z refleksji na LambdaMetafactory [ZOBACZ JAK].
Pax et bonum!