Zaraz koniec roku, trzeba zamknąć pewne tematy.
Ostatni wpis w tej serii jest o optymalizacjach związanych z finalnymi polami instancyjnymi. Zacznę od obiecanego powrotu do tematu z pól statycznych finalnych, czyli obiektu trzymanego w polu statycznym finalnym oraz jego pól.
Gdy właściciel pola finalnego sam jest polem static final
…
Weźmy pod uwagę hipotetyczną sytuację: hipotetyczny obiekt Owner
, który jest przetrzymywany w polu static final
posiada pole finalne int value
. W jakiejś innej klasie odwołujemy się przez to pole static final
do klasy Owner
, a następnie do tego int value
.
W czasie kompilacji JIT mamy informację dokładnie o ustalonej wartości tego pola value
, zatem można by oczekiwać, że w ramach optymalizacji zostanie wykonany Constant Folding
. Jaka jest rzeczywistość? Wykonajmy benchmark:
@State(Scope.Benchmark) public class InstanceFinalFieldBenchmark { public static final ConstantClass CONSTANT_CLASS = new ConstantClass(12); public static class ConstantClass { private final int value; public ConstantClass(int value) { this.value = value; } public int getValue() { return value; } } @Benchmark @CompilerControl(CompilerControl.Mode.PRINT) public int local() { return CONSTANT_CLASS.getValue(); } }
Adnotacja @CompilerControl(CompilerControl.Mode.PRINT)
powoduje wypisanie kodu maszynowego dla tej metody. Spoglądając w jej treść odnajdziemy fragment:
0x00007f487be1f05c: movabs $0x716202b90,%r10 ; {oop(a 'pl/jgardo/field/instance/InstanceFinalFieldBenchmark$ConstantClass'{0x0000000716202b90})} 0x00007f487be1f066: mov 0xc(%r10),%eax ;*getfield value {reexecute=0 rethrow=0 return_oop=0} ; - pl.jgardo.field.instance.InstanceFinalFieldBenchmark$ConstantClass::getValue@1 (line 23) ; - pl.jgardo.field.instance.InstanceFinalFieldBenchmark::local@3 (line 58)
Te dwie instrukcje odpowiadają za pobranie wartości z pola klasy, co jest też objaśnione komentarzem getfield value
.
Innymi słowy brakuje tutaj tej optymalizacji, której byśmy oczekiwali. Dlaczego tak się dzieje?
OpenJDK ma problem z zaufaniem do instancyjnych pól finalnych.
Dzieje się tak z powodu, że istnieje kilka sposobów na popsucie pola final
. Te sposoby to:
- metody natywne,
- refleksja,
- method handles,
- sun.misc.Unsafe.
(m. in. o tych sposobach jest prezentacja Volkera Simonisa „How final is final”, którą polecam 😉 ).
Niestety póki co nie jesteśmy w stanie za dużo zrobić, by final
domyślnie odzyskał pełną wiarygodność. Jakkolwiek są pewne dość brudne sposoby, by zmusić JVMa do zaufania finalom.
-XX:+TrustFinalNonStaticFields
Pierwszy to eksperymentalna flaga -XX:+TrustFinalNonStaticFields
. Niestety istnieje ona w OpenJDK w wersji 8 i późniejszych, lecz w OracleJDK była w wersji 8, a w 11 już nie…
Jeśli chodzi o skuteczność tej flagi, to w OpenJDK działa ona zgodnie z przewidywaniem, tzn zwraca od razu żądaną wartość:
0x00007f95c4440bdc: mov $0xc,%eax
Jeśli ktoś chciałby co nieco poczytać na temat tej flagi, to polecam spojrzeć na tę korespondencję mailową.
@jdk.internal.vm.annotation.Stable
Drugim sposobem na zmuszenie JVMa do zaufania final
jest użycie adnotacji @Stable
na danym polu finalnym. Taka adnotacja istnieje od OpenJDK w wersji 9, jednak została ona zaprojektowana tylko i wyłącznie do użytku wewnętrznego JVM i nie jest zbyt łatwo dostępna dla zwykłych śmiertelników.
Nie oznacza to jednak, że się nie da jej użyć… 😉
Istnieją dwa ograniczenia zniechęcające do użycia jej:
- Adnotacja jest dostępna tylko dla modułów:
java.base
,jdk.internal.vm.ci
,jdk.unsupported
- Ale jeśli dodamy przy kompilacji obiektu korzystającego ze
@Stable
parametry--add-exports java.base/jdk.internal.vm.annotation=ALL-UNNAMED
to się skompiluje,
- Ale jeśli dodamy przy kompilacji obiektu korzystającego ze
- Obiekt korzystający ze
@Stable
musi być załadowany przez bootclassloader- Zatem jeśli dodamy przy uruchomieniu parametr -Xbootclasspath/a:””, to też zadziała 😉
Ludzie listy piszą…
Na temat @Stable
również istnieje korespondencja mailowa, na którą warto spojrzeć. Dotyczyła ona pytania, dlaczego by nie udostępnić takiej adnotacji dla użytkowników. W tej korespondencji jest wiele ciekawych wątków i linków.
W jednej z odpowiedzi można znaleźć trzeci sposób na zasymulowanie @Stable
. Jednak nie testowałem, więc się nie wypowiem.
Co robić, jak żyć?
Jest pewna nadzieja – na samym końcu wspomnianej wyżej korespondencji jest taka wypowiedź:
For optimizing final fields there are much more promising approaches: (1) optimistic optimizations in JITs: treat finals as constants and track updates invalidating code which relies on that (there were some experiments, e.g. [1]); (2) forbid final field updates at runtime after initialization is over and freely optimize them in JITs. Both approaches still have some roadblocks on their way (deserialization relies on modifying final fields, hard to track final field values of individual objects in JVM, etc), but the platform steadily moves in the direction of treating final fields as truly final by default.
Zatem trzeba to przyjąć z pokorą i cierpliwością, bo pisanie JVMów do łatwych nie należy…
Chyba, że się jest bogatym, to zamiast cierpliwie czekać, można zainwestować w Azul Zing – tam jest wiele ciekawych flag do użycia z „final” w treści (na stronie chriswhocodes.com można podejrzeć, jakie są dostępne opcje/flagi dla różnych JVMów; można wyszukać po nazwie opcji).
Chociaż osobiście jeszcze nie zgłębiałem możliwości tej JVMki.
A co z polami instancyjnymi?
Jak się okazuje, śledzenie finalnych pól obiektu static final
jest nietrywialne, a jeszcze trudniejsze jest śledzenie wartości w polach niestatycznych… Nie znalazłem niestety żadnych optymalizacji dla pola finalnego…
Tym smutnym faktem kończę całą tę sagę o final
. Ostatecznie Frodo nie dotarł do Góry Przeznaczenia, Golum odebrał mu pierścień, a słowo kluczowe final
dla pól instancyjnych nie ma de facto pozytywnego wpływu na wydajność…
Ale głowy do góry, nadchodzi nowy rok 2020, a z nim nowe, czternaste wydanie OpenJDK, gdzie ma zostać pokazany światu po raz pierwszy nowy sposób dostępu do Off-Heapa. Jednocześnie to może być kolejny krok w stronę zmniejszenia znaczenia sun.misc.Unsafe
. A to może w skończonym czasie doprowadzić do wzrostu znaczenia final
a.
Cytując klasyka -„Make final final again”. Czy coś podobnego… 😉