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 JITem. Trzeba zatem sprawdzić, czy taki kod istnieje i go wywołać.
- Jeśli metoda nie jest skompilowana JITem, to wywoływana jest metoda natywna uruchamiająca kod metody.
Warto zauważyć, iż 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 JITa 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 ulepszenia 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, iż 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ą, iż 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!