Dostałem reklamacje od kumpla, który spodziewał się jakichś benchmarków porównujących wydajność konkatenacji w kolejnych wersjach JVMów. Oczywiście chodzi o dopełnienie poprzedniego wpisu.
Co sprawdzamy i jak?
Założyłem, że porównam działanie kodu skompilowane różnymi wersjami kompilatorów javac
z JDK 1.4, 1.8 i 15. Następnie te skompiowane kody wrzucę do plików Jar, które będę uruchamiał na kolejnych wersjach JVMa.
Nie wiem, ilu z Was kompilowało kod z poziomu terminala. Osobiście preferuję skorzystanie z Mavena
, ewentualnie z poziomu IDE. Jednak w ramach ćwiczenia stwierdziłem, że skompiluję i spakuję do Jar
z użyciem linii komend. Dla jednego pliku nie okazało się to zbyt trudne 😉
javac src/main/java/dev/jgardo/jvm/miscellaneous/string/StringConcatenation.java jar cf ./string.jar dev/jgardo/jvm/miscellaneous/string/StringConcatenation.class
Następnie uruchamiałem następujące benchmarki w JVMek różnych wersji (1.7,1.8,11,15 – wszystkie z rodziny OpenJDK
), które korzystają z bibliotek skompilowanych różnymi wersjami javac
.
private static final StringConcatenation CONCATENATION = new StringConcatenation(); @Benchmark public String concatenation() { return CONCATENATION.helloWorldFromOldMen(12); }
Gdzie StringConcatenation
to:
public class StringConcatenation { public String helloWorldFromOldMen(long age) { return "Hello " + " world from " + age + " years old men"; } }
Wyniki
JVM \ jar | 1.4 [ns/op] | 1.7/1.8 [ns/op] | >9 [ns/op] |
OpenJDK 1.7 | 101,435 ± 2,746 | 66,310 ± 1,106 | – |
OpenJDK 1.8 | 98,539 ± 1,757 | 68,302 ± 1,228 | – |
OpenJDK 11 | 96,123 ± 1,137 | 54,094 ± 2,117 | 23,195 ± 0,172 |
OpenJDK 15 | 83,235 ± 1,837 | 55,243 ± 2,067 | 23,516 ± 0,301 |
Wyniki w zależności od wersji maszyny wirtualnej oraz wersji kompilatora javac
. Wyniki wyrażone w ns/op.
Z tych wyników można wyciągnąć kilka wniosków:
StringBuffer
jest wolniejszy odStringBuildera
Mało odkrywcze – dodatkowa synchronizacja zawsze coś będzie kosztować. Jakkolwiek wydaje się, że w tych nowszych JVMkachStringBuffer
przyspieszył zawsze jest przynajmniej 1/3 wolniejszy niżStringBuilder
- Uruchomienie konkatenacji skompilowanej
javac
w wersji 1.8 jest szybsze na OpenJDK 11 o około 20% niż na OpenJDK 1.8.
To w prawdopodobnie wynika z tego, że w Java 9 zaczęto używać 1 bajtowegobyte
zamiast 2 bajtowegochar
. Więcej o tym choćby tutaj – JEP-254. - Uruchomienie konkatenacji skompilowanej
javac
w wersji 9 wzwyż powoduje skrócenie czasu o ok. 55%.
O tym efekcie wspominałem już w poprzednim wpisie. Notatka eksperymentalna zawierała prawdę 😉
Pamięć
Zmierzyłem również ilość potrzebnej pamięci do wykonania konkatenacji. Również nie było to trudne – wystarczyło do benchmarku dodać jedną linijkę podpinającą GCProfiler
. Wyniki w poniższej tabelce.
JVM \ jar | 1.4 [B/op] | 1.7/1.8[B/op] | >9[B/op] |
OpenJDK 1.7 | 272,000 ± 0,001 | 272,000 ± 0,001 | – |
OpenJDK 1.8 | 272,000 ± 0,001 | 272,000 ± 0,001 | – |
OpenJDK 11 | 200,000 ± 0,001 | 168,000 ± 0,001 | 80,000 ± 0,001 |
OpenJDK 15 | 200,028 ± 0,002 | 168,019 ± 0,001 | 80,009 ± 0,001 |
Wyniki w zależności od wersji maszyny wirtualnej oraz wersji kompilatora javac
. Wyniki wyrażone w B/op.
Również i tutaj jestem winien kilku słów komentarza:
StringBuilder
iStringBuffer
uruchomione na OpenJDK w wersji 9 wzwyż korzystają z wspomnianego wcześniej ulepszenia – JEP-254. Stąd redukcja o 25% zużycia pamięci względem uruchomienia na wersji 1.7 lub 1.8.- Użycie konkatenacji skompilowanej
javac
w wersji 9 wzwyż pozwala na redukcję zużycia pamięci o 50% w porównaniu do konkatenacji skompilowanejjavac
w wersji 1.8 i o 67% w porównaniu do wersji 1.4.
Podsumowanie
Warto używać nowszej wersji Javy niż owiana sławą 1.8. Wbrew pozorom w nowych wersjach Javy wchodzą nie tylko nowe feature’y, lecz i usprawnienia wydajności.
O ile konkatenacja w Javach 9+ jest znacznie bardziej wydajna, to rzadko kiedy jest to na tyle kluczowe, by zastąpić czytelne String.format
, Logger.info
itd. Czytelność jest ważna, a wydajność konkatenacji stringów może mieć marginalne znaczenie, jeśli macie znacznie cięższe operacje do wykonania – operacje na bazie danych lub uderzenie HTTP na zewnętrzny serwis.
Warto też spojrzeć na minimalną wersję Javy wymaganą przez biblioteki jako na potencjalną możliwość przyspieszenia działania, zamiast wyłącznie na ograniczenie.
Pax