Java 12 - nowe funkcje

1

Dzisiaj Java 12 wejdzie w fazę General Availability. Po wielu nowościach, dostarczonych wraz z Javą 11, tym razem mamy do czynienia z nieco skromniejszym wydaniem, które nie jest LTS-em. Zobaczmy, co oferuje programistom ta wersja Javy.

Wyrażenie Switch
Chyba najgorętsza nowość, o której mówi się w internecie. Switch od teraz jest już wyrażeniem. Oprócz tego pojawiła się nowa składnia, z której można skorzystać w trybie preview (dzięki przełącznikowi --enable-preview).

Od tej pory archiwum class data-sharing (CDS) będzie generowane automatycznie.
Zapewnia to JEP 341. CDS pomaga w szybszym starcie aplikacji. Dla HelloWorld to aż 32%. Jest więc o co grać, dlatego w czasie kompilacji z automatu zostanie dodane archiwum z CDS do wyprodukowanego obrazu.

Więcej nowości opisał Adam (dobry człowiek) tutaj: https://bulldogjob.pl/news/545-java-12-nowe-funkcje

1

No to teraz w Javie mamy dwa GC z niskimi opóźnieniami:

ZTCW to jednak Shenandoah jest wycięty z buildów przygotowanych przez Oracle'a i trzeba poczekać, aż pełne buildy OpenJDK pojawią się np na https://adoptopenjdk.net/ albo w jakichś innych repozytoriach. Sprawdzę później :)

0

@Wibowit: warto dodacze z tymi GC to cos za cos: mamy krotkie opoznienia, ale spada przepustowosc.

0

Według slajdów Oracle'a przepustowość pod ZGC jest tylko kilka procent gorsza niż pod G1. Zależy co jest większym problemem w danym przypadku: duże przestoje co jakiś czas czy trochę mniejsza ogólna przepustowość. Teraz to przynajmniej w Javie jest wybór, bo dla przykładu w Go ( https://golang.org/ ) jest nacisk tylko i wyłącznie na zmniejszanie pauzy GC, kosztem zarówno przepustowości jak i narzutu pamięciowego: https://blog.golang.org/ismmkeynote

0

Przetestowałem Javę 12 i Go w benchmarku binarytrees, gdyż jest on dość pesymistycznym przypadkiem jeśli chodzi o automatyczne odśmiecanie.

Na początek wersje:

Wersje oprogramowania:

$ lsb_release -a
LSB Version:	core-9.20160110ubuntu0.2-amd64:core-9.20160110ubuntu0.2-noarch:security-9.20160110ubuntu0.2-amd64:security-9.20160110ubuntu0.2-noarch
Distributor ID:	Ubuntu
Description:	Ubuntu 16.04.6 LTS
Release:	16.04
Codename:	xenial
$ go version
go version go1.6.2 linux/amd64
$ ~/devel/jdk-12/bin/java -version
openjdk version "12" 2019-03-19
OpenJDK Runtime Environment (build 12+33)
OpenJDK 64-Bit Server VM (build 12+33, mixed mode, sharing)

Wyniki wydajności:

 time ./binarytrees.go-5.go_run 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m14.385s
user	0m54.290s
sys	0m0.254s
$ time ~/devel/jdk-12/bin/java binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m2.425s
user	0m7.081s
sys	0m0.726s
$ time ~/devel/jdk-12/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m4.068s
user	0m8.820s
sys	0m3.112s

Jak widać Go radzi sobie bardzo słabo. Trzeba jednak wziąć pod uwagę iż ZGC zaalokował sobie sporo pamięci u mnie. Co się stanie jak dam w Javie mniejszą stertę?

G1 GC zachowuje się praktycznie tak samo:

$ time ~/devel/jdk-12/bin/java binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m2.425s
user	0m7.081s
sys	0m0.726s
$ time ~/devel/jdk-12/bin/java -Xmx2g binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m2.402s
user	0m7.495s
sys	0m0.353s
$ time ~/devel/jdk-12/bin/java -Xmx1g binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m2.465s
user	0m7.857s
sys	0m0.222s

ZGC natomiast sporo zwalnia, ale i tak jest dużo szybsze od Go:

$ time ~/devel/jdk-12/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m4.068s
user	0m8.820s
sys	0m3.112s
$ time ~/devel/jdk-12/bin/java -Xmx2g -XX:+UnlockExperimentalVMOptions -XX:+UseZGC binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m4.879s
user	0m11.839s
sys	0m1.076s
$ time ~/devel/jdk-12/bin/java -Xmx1g -XX:+UnlockExperimentalVMOptions -XX:+UseZGC binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m7.458s
user	0m15.141s
sys	0m0.562s

Jeśli dalej przytniemy stertę to ZGC już zwalnia prawie do poziomu Go, chociaż G1 GC nadal utrzymuje wysoką przepustowość:

$ time ~/devel/jdk-12/bin/java -Xmx500m binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m3.144s
user	0m9.816s
sys	0m0.197s
$ time ~/devel/jdk-12/bin/java -Xmx500m -XX:+UnlockExperimentalVMOptions -XX:+UseZGC binarytrees 21
stretch tree of depth 22	 check: 8388607
2097152	 trees of depth 4	 check: 65011712
524288	 trees of depth 6	 check: 66584576
131072	 trees of depth 8	 check: 66977792
32768	 trees of depth 10	 check: 67076096
8192	 trees of depth 12	 check: 67100672
2048	 trees of depth 14	 check: 67106816
512	 trees of depth 16	 check: 67108352
128	 trees of depth 18	 check: 67108736
32	 trees of depth 20	 check: 67108832
long lived tree of depth 21	 check: 4194303

real	0m13.543s
user	0m23.298s
sys	0m0.305s

Dalsze przycinanie byłoby już chyba trochę nie do końca sensowne, bo obiekty w Javie mają raczej trochę większy narzut pamięciowy niż obiekty w Go, a samo ZGC nie jest przygotowane do małych stert.

Shenandoah nie jest dostępne w buildzie od Oracle'a, czyil w buildzie pod adresem: http://jdk.java.net/12/

$ time ~/devel/jdk-12/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC binarytrees 21
Error occurred during initialization of VM
Option -XX:+UseShenandoahGC not supported

real	0m0.005s
user	0m0.000s
sys	0m0.005s
0

@Wibowit: ten benchmark do Go z BT jest totalnie z czterech liter, bo nie używa się tam sync.Pool, którego masz w standardowej bibliotece dostępnego (za to w niektórych innych językach custom allocatory są używane jakoś bez przeszkód). Zresztą, tam prawie każdy "benchmark" jest totalnie oderwany od rzeczywistości. Po prostu nikt ogarnięty tego BT tak w Go nie napisze.

0

W tych benchmarkach chodzi o sposób osiągnięcia celu, a nie o cel sam w sobie. Gdyby chodziło tylko o wypisany wynik, to benchmark binarytrees można by załatwić kilkoma printfami.

Wiem, że niektóre implementacje stosują pule, a niektóre nie. Łatwo to jednak wyczaić - wystarczy zajrzeć do kodu i poszukać jakiegoś "include pool".

Przy domyślnych ustawieniach i bez użycia pul, alokacja i odśmiecanie w Javie jest 2x szybsze niż new/delete w C++ i 8x szybsze niż alokacja i odśmiecanie w Go. Takie coś miał testować ten benchmark.

0
Wibowit napisał(a):

W tych benchmarkach chodzi o sposób osiągnięcia celu, a nie o cel sam w sobie. Gdyby chodziło tylko o wypisany wynik, to benchmark binarytrees można by załatwić kilkoma printfami.

Wiem, że niektóre implementacje stosują pule, a niektóre nie. Łatwo to jednak wyczaić - wystarczy zajrzeć do kodu i poszukać jakiegoś "include pool".

Przy domyślnych ustawieniach i bez użycia pul, alokacja i odśmiecanie w Javie jest 2x szybsze niż new/delete w C++ i 8x szybsze niż alokacja i odśmiecanie w Go. Takie coś miał testować ten benchmark.

Tak, o tym właśnie mówię. Sposób osiągnięcia celu w Go jest dziwny. Nikt tak tego nie pisze. Za to jak autor tejże strony pisze, że to tylko zabawa i w takich ramach, zabawy właśnie, bym te benchmarki traktował. Czyli nic, co ma jakieś miarodajne przełożenie na cokolwiek. Można wyprodukować dowolnie wolny kod np. w C++, a superszybki w jakimś slamazarnym języku i taki kod C++, mimo iż będzie pisany w erze paleozoicznej C++, to go przyjmą, bo to tylko zabawa.

0

Mnie interesuje wydajność generycznych mechanizmów do zarządzania pamięcią, a nie pule, których mało kto używa, bo nadają się tylko do specyficznych zastosowań. Pule widziałem tylko i wyłącznie w benchmarkach. Jeśli interesuje cię wydajność pul to może podeślij rozwiązanie oparte na pulach? Mnie to i tak zupełnie nie obchodzi :]

Pokaż mi jakiś otwartoźródłowy projekt w Go, w którym pule są szeroko wykorzystywane. Zobaczymy na ile to wykorzystanie pul jest miarodajne.

0
Wibowit napisał(a):

Mnie interesuje wydajność generycznych mechanizmów do zarządzania pamięcią, a nie pule, których mało kto używa, bo nadają się tylko do specyficznych zastosowań. Pule widziałem tylko i wyłącznie w benchmarkach. Jeśli interesuje cię wydajność pul to może podeślij rozwiązanie oparte na pulach? Mnie to i tak zupełnie nie obchodzi :]

W tym właśnie problem, Ci mówię, że tak się w Go nie robi + inni pisali już bez puli BT i osiągali lepsze czasy (stdlib), dlatego ten "benchmark" nie jest zupełnie miarodajny w Go (nie wiem jak w innych językach - słyszałem podobne opinie)

Pokaż mi jakiś otwartoźródłowy projekt w Go, w którym pule są szeroko wykorzystywane. Zobaczymy na ile to wykorzystanie pul jest miarodajne.

Grep w moim GOPATH zwraca mi za dużo wyników (dokładnie tysiąc), więc podam Ci ostatni z listy: zerolog.

0

W tym właśnie problem, Ci mówię, że tak się w Go nie robi + inni pisali już bez puli BT i osiągali lepsze czasy (stdlib), dlatego ten "benchmark" nie jest zupełnie miarodajny w Go (nie wiem jak w innych językach - słyszałem podobne opinie)

Jeśli chcę porównać narzut na read barrier, write barrier i ogólnie na tracing GC to wykorzystanie puli mi w tym tylko i wyłącznie przeszkodzi.

Grep w moim GOPATH zwraca mi za dużo wyników (dokładnie tysiąc), więc podam Ci ostatni z listy: zerolog.

Biblioteka do logowania to raczej nie jest duży projekt. W Javce też są tego typu loggery (w sensie bez śmiecenia w pamięci):
https://logging.apache.org/log4j/2.x/manual/garbagefree.html
https://logging.apache.org/log4j/2.x/manual/async.html

Z tego co widzę na https://golang.org/src/sync/pool.go te pule w Go to wcale nie mają jakiegoś ręcznego reseta (jak te w C/C++) a służą raczej do odśmiecania podczas pauzy GC (WTF? przecież w Go wykonali masę roboty, by pauzy były jak najkrótsze) - widać to w komentarzu wewnątrz funkcji poolCleanup().

Ponadto w benchmarks game jest jedno rozwiązanie, które wykorzystuje sync.Pool i, uwaga, jest najwolniejsze, kilka razy wolniejsze od najszybszego!
https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/binarytrees.html
Najszybsze rozwiązanie w Go: Go #5 - 26.94s
Jedyne rozwiązanie korzystające z sync.Pool: Go #6 - 123.44s

0

@TurkucPodjadek Nie rozumiem jak sync.Pool mógłby coś pomóc w wynikach: ten problem jest nastawiony na jak największy throughput, a nie latency. Dodanie czy wyciągnięcie zasobu z tej puli jest bardzo skomplikowane (można podejrzeć https://golang.org/src/sync/pool.go?s=1633:1992#L88) w porównianiu do Javowego modelu alokacji typu bump pointer (przynajmniej dla G1, nie wiem jak to robi ZGC). To, że alokacja pamięci/GC w golang nie wyrabiają w tego typu problemach to tylko wina języka/platformy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1