W tym artykule chciałbym opisać jedną z ciekawszych zmian jakie nas czekają w nowej Javie, a dokładniej mowa o projekcie Valhalla prowadzonym przez OpenJDK.
O samym projekcie w społeczności słyszy się już od jakiegoś czasu, bo od ok. 2014 roku. Coś tam o oszczędzaniu pamięci, coś o przyspieszeniu… Ale o co tak na prawdę chodzi?
Tyle czasu działało, co się zmieniło?
Na początku małe historyczne wprowadzenie. Dawno temu, kiedy projektowana była Java (połowa lat 90) dostęp do pamięci był porównywalny z kosztem wykonywania operacji arytmetycznych. Od tego czasu dużo się zmieniło. Różnica pomiędzy tymi operacjami bardzo mocno się zwiększyła. Dostęp do pamięci jest kilkaset razy kosztowniejszy.
(almost) everything is an object
Zdanie, które bardzo często słyszymy w kontekście Javy.
Nie wszystko w Javie jest obiektem. Aktualnie istnieje istotny podział na typy prymitywne (primitive types):
- boolean,
- character,
- int,
- float,
- double
oraz referencje (reference type):
- arrays,
- classes,
- interfaces,
- null pointer
Ważną informacją tutaj jest to, iż lokalnie zadeklarowane typy prymitywne są składowane na stosie natomiast (nawet) lokalnie zadeklarowane obiekty są składowane na stercie, jedynie referencje do nich są składowane na stosie.
Z tego też powodu odwołanie się do typu referencyjnego jest dłuższe, bo jest to dopiero wskaźnik
do
docelowej
wartości.
Czyli wymagany jest skok pod adres w pamięci, aby odczytać wartość, która się pod nią znajduje w przeciwieństwie do typu prymitywnego gdzie wartość dostajemy od razu, bezpośrednio.
In computer programming, data types can be divided into two categories: value types and reference types. A value of value type is the actual value. A value of reference type is a reference to another value
https://en.wikipedia.org/wiki/Value_type_and_reference_typeW przypadku Javy value types to właśnie typy prymitywne.
Skoro już wiemy czemu typy prymitywne są szybsze możemy przejść do głównych założeń projektu Valhalla.
Prawdopodobnie nie raz mieliście potrzebę zrobienia sobie klasy, która trzymała Wam w kupie jakiś zbiór informacji – chociażby klasa do liczb zespolonych lub bardziej przyziemnie, punkt na płaszczyźnie. Tak na prawdę jedyne czego oczekujemy, to aby zmienne x i y były wrzucone do jednego worka. W takim przypadku nie interesują nas pozostałe zalety klas (dziedziczenie, polimorfizm). Wyobraźmy sobie, iż takich punktów na płaszczyźnie mamy dużo, bardzo dużo. Pakujemy te obiekty do listy. I w tym momencie pojawia się problem – jak kosztowne jest przechodzenie po takiej tablicy? Co więcej, wewnętrznie mamy intuicję, iż tracimy sporo miejsca na dane związane z narzutem informacji o obiekcie (czujesz chyba, iż int jest lżejszy od Integera?).
Łatwo sobie wyobrazić, iż będziemy musieli odwoływać się za każdym razem do obiektu poprzez referencję. Wiemy już, iż jest to o wiele kosztowniejsze niż odwoływanie się do typu prymitywnego. W takim razie najlepszym rozwiązaniem byłoby traktować te punkty jako typy prymitywne, bo tak na prawdę tym dokładnie (w naszym zamiarze) są.
Podsumowując: nie zawsze potrzebujemy całego narzutu związanego z klasą. Czasami chcemy po prostu definiować własne prymitywne typy.
Value types – Codes like a class, works like an int
Ideą value types jest reprezentowanie skupionych czystych (bez żadnych dodatkowych informacji) danych. Aktualnie w przypadku obiektu w pamięci znajdują się dwie sekcje:
nagłówek (header area) z informacjami dla garbage collectora, wskaźnikiem do klasy obiektu etc. oraz dane (data area).
Jeżeli chodzi o typy prymitywne np. int w pamięci jest po prostu sekcja z danymi.
Oszczędzamy dzięki temu miejsce, które normalnie byłoby wykorzystywane na nagłówek oraz wskaźnik.
Wróćmy teraz do naszego przykładu z wieloma punktami na płaszczyźnie zapisanymi w liście. Dzięki czystym danym lista może zostać spłaszczona.
A w efekcie uzyskujemy spore przyspieszenie operowania na takiej liście oraz oszczędzamy miejsce. Wszystkie elementy są lokalnie blisko siebie, nie musimy skakać po całej pamięci.
because we don’t waste space on object headers and pointers, which can increase memory usage by up to 4x
[…]
Which is the real point — that we need not force users to choose between abstraction/encapsulation/safety and performance.
We can have both. Whether you call that „cheaper objects” or „richer primitives”, the end result is the same.
Specialized Generics
Trochę skłamałem z tym operowaniem na typach prymitywnych w liście. Próba zadeklarowania listy generycznej z typem prymitywnym nam się niestety nie uda.
Próby obejścia (np. nie wpisywanie typu i dodanie do listy zwykłego inta) również się nie powiodą – element zostanie automatycznie opakowany we wrapper w tym przypadku klase Integera. Dzieje się tak, ponieważ generyki aktualnie nie przyjmują typu prymitywnego jako argumentu.
No ale o tym też mądre głowy pomyślały. Valhalla umożliwić ma również własnie wykorzystywanie typów prymitywnych w generykach.
Extend generics to allow abstraction over all types, including primitives, values, and even void;
Nic tylko czekać, a zdaje się, iż to już niedługo…
Więcej do poczytania:
https://wiki.openjdk.java.net/display/valhalla/Valhalla_Goals
http://openjdk.java.net/jeps/169
https://openjdk.java.net/jeps/218
https://dzone.com/articles/what-is-project-valhalla