Tydzień temu skupiliśmy się głównie na nowych wydaniach, dzisiejsza edycja zaś poświęcona została w pełni niskopoziomowym nowościom, których ostatnio również mieliśmy wysyp.
1. Co nowego w Projekcie Leyden?
Są projekty w JVM, które w większym stopniu niż inne przebijają się do masowej świadomości. Większość ludzi słyszała o Loomie i Valhalli, pewnie Panama też istnieje w świadomości społeczności (choć jestem skłonny się założyć, że większość osób nie chciałaby mieć z nią za wiele do czynienia w praktyce). Tymczasem istnieją też mniej znane projekty, takie jak Leyden, które związane są głęboko z technicznym aspektem JVM. Mimo ich specjalistycznego charakteru, są one (przynajmniej dla mnie) niesamowicie fascynujące. Dlatego cieszę się, że mając świeże informacje prosto z JVM Language Summit, mogę jeszcze raz przybliżyć cele, ale też zarysować Wam najnowsze kierunki rozwoju tego projektu.
Leyden radykalnie zmienia optymalizację Javy, wprowadzając technikę, która w oryginalne brzmi „shifting and constraining”, co w moim luźnym tłumaczeniu oznacza „przesuwania i ograniczania” obliczeń. Zamiast polegać wyłącznie na kompilowaniu w runtime, Leyden przesuwa niektóre z niezbędnych operacji optymalizacji do wcześniejszych faz, w zależności od wymagań konkretnego programu i środowiska uruchomieniowego – żyjemy w końcu w świecie autoscalingu, chmur i innych kubernetesów.
Głównym nowym klockiem wprowadzanym przez Leyden są tak zwane „kondensatory” (condensers). Można je sobie wyobrazić jako specjalizowane narzędzia działające sekwencyjnie, udoskonalając kod aplikacji. Proces rozpoczyna się od bazowej, konfiguracji aplikacji. Przechodząc przez każdy kondensator, wprowadzane są transformacje, aż osiągnie się punkt końcowy: zoptymalizowaną wersję przeznaczoną do wydajnego czasu wykonania.
Aby przeprowadzić analogię, mechanizm kondensatora Leyden przypomina middleware w ramach internetowych takich jak Express.js dla Node.js. W rozwoju internetowym middleware przechwytuje żądania, przetwarza je i ewentualnie modyfikuje przed dotarciem do ich docelowego miejsca. Podobnie kondensatory Leyden przyjmują ApplicationModel
— niezmienne przedstawienie aplikacji — i wytwarzają jego udoskonaloną wersję. Podejście to zapewnia, że oryginalny model pozostaje niezmieniony, a optymalizacje tworzą nowe, ulepszone wersje. Także kod jest stosunkowo prosty.
interface Condenser {
ApplicationModel condense(ApplicationModel model);
}
Oczywiście, cała magia opiera się w samej klasie ApplicationModel
, ale chętnych zachęcam do sprawdzenia artykułu Toward Condensers autorstwa Briana Goetza, Marka Reinholda, & Paula Sandoza. Tam też znajdziecie więcej szczegółów i przykładów kodu. Ja tu zostawię jeszcze jeden przykładowy sample, który myślę deskryptywne obrazuje użycie:
@Override
public ApplicationModel condense(ApplicationModel model) {
ModelUpdater updater = model.updater();
Stream.concat(model.modules(), model.classPath())
.flatMap(model::classes)
.forEach(classKey -> {
ClassModel cm = if (cm != null) {
/// do transformation on Classes
updater.addToContainer(classKey, ClassContents.of(cm));
}
});
return updater.apply();
}
Jednak Leyden nie opiera się wyłącznie wyłącznie na statycznej transformacji. Obecnie Java używa strategii „kompilacji warstwowej” (Tiered Compilation) do osiągnięcia szczytowej wydajności. Zacząwszy od Poziomu 0, gdzie JVM zaczyna interpretować bajtkod, aplikacje stopniowo przechodzą do Poziomu 4, który korzysta już z zoptymalizowanego kodu. Kolejne etapy osiągane są już podczas działania aplikacji. Jeśli potrzebujecie więcej, świetny tekst How Tiered Compilation works in OpenJDK na ten temat opublikował niedawno Cesar Soares z Microsoftu. Leyden zaś udoskonala ten proces.
Wspomniana już aktualizacja z JVM Language Summit wprowadza tak zwane „biegi treningowe”. Są to w symulacje działania aplikacji, mające na celu zebranie danych behawioralnych. Analizując zachowanie aplikacji w różnych scenariuszach w runtime, kondensatory mogą uzyskać kluczowe informacje o zachowaniu aplikacji w produkcyjnym środowisku. Dzięki temu wprowadzane udoskonalenia opierają się nie tylko na teoretycznych modelach, ale są zakorzenione w rzeczywistych dynamikach aplikacji – tak jak ma to miejsce np. w Profile-Guided Optimizations (PGO) w GraalVM. O GraalVM zresztą też można myśleć jak o kolejnym kondensatorze.
Wykorzystując informacje uzyskane podczas „biegów treningowych” (o których za chwile), Leyden wprowadza kluczowe optymalizacje jeszcze przed rozpoczęciem działania. Dlatego, gdy aplikacja rozpoczyna standardową warstwowy system Javy, ma już przewagę. To proaktywne podejście nie tylko przyspiesza proces „rozgrzewania”, ale umożliwia dłuższe okresy szczytowej wydajności okresy szczytowej wydajności.
Jeżeli jesteście ciekawi, jak w praktyce prezentuje się Leyden, Brian Goetz opublikował analizę Condensing Indy Bootstraps. Opisuje ona, jak cały proces wygląda w wypadku generowania dodatkowego kodu na np. Na potrzeby lambd dzięki instrukcjom Invoke Dynamics. To wymaga jednak bazowej wiedzy na temat tego jak w tej chwili wygląda działanie instrukcji invokedynamic
czy obiektów CallSite
i MethodHandle
. Dodatkowo, pojawił się jeszcze jeden dodatkowy, zaawansowany tekst pokazujący użycie znajdującego się jeszcze w draftcie Computed Constants, o których pisałem dwie edycje temu. Traktujcie więc oba jako lekturę nadobowiązkową 😄
Zainstaluj teraz i czytaj tylko dobre teksty!
2. Co nowego w Projekcie Valhalla?
Dawno nie było o Valhalli, co? Na szczęście nie znaczy to, że nic ciekawego się nie działo. Projekt ulega dalszej rewolucji i z każdą kolejną iteracją staje się coraz… prostszy. Jeszcze w czerwcu pisałem o uproszczeniach przy okazji obsługi nullowalności, teraz zaś uproszczenia odbywają się na poziomie maszyny wirtualnej. Żeby jednak lepiej zrozumieć o co chodzi – odrobinka teorii.
Tak zwane L-typy reprezentują tradycyjne typy referencyjne w Java Virtual Machine (JVM). Są to więc podstawowe deskryptory (parametry opisujące) klas Java, z którymi programiści pracują od lat. Gdy „zwykły” obiekt Java jest tworzony, istnieje on w pamięci, a z poziomu kodu operujemy na jego referencji. Ten mechanizm zapewnia, że operacje na obiekcie zmieniają jego stan w scentralizowanym miejscu.
Valhalla wprowadza zaś Value Classy, będące pojemnikami na dane bez własnej tożsamości. Dla przykładu, jeśli mielibyśmy klasę wartościową reprezentującą punkt w przestrzeni ze współrzędnymi X i Y, dwie instancje o tych samych współrzędnych są nieodróżnialne, w przeciwieństwie do tradycyjnych obiektów, gdzie nawet jeśli dane są takie same, każdy obiekt ma unikalną tożsamość.
Dlaczego jest to istotne? Przekazywanie danych przez wartość może prowadzić do poprawy wydajności, zwłaszcza przy intensywnym korzystaniu z danych. Co więcej, oferuje bardziej przewidywalne i zwięzłe zachowanie, zwłaszcza w środowiskach wielowątkowych, gdyż nie ma efektów ubocznych związanych z odniesieniami.
Jednak wprowadzenie klas wartościowych nie było proste. Początkowo nowe Q-typy na poziomie JVM oraz tak zwany bajtkod v (czyli warianty większości operacji z przedrostkiem v, jak na przykład vload
będący odpowiednikiem istniejącego aload
) były uważane za konieczne do operowania na tych nowych strukturach danych. Wymagałoby to od programistów i JVM obsługi dwóch odrębnych systemów, co mogło komplikować kod i zmniejszać korzyści wynikające z korzystania z klas wartościowych.
Ale w miarę dojrzewania projektu Valhalla okazało się jednak, że tradycyjne L-typy, z pewnymi modyfikacjami i optymalizacjami, mogłyby potencjalnie reprezentować Value Class. Można się więc obyć bez oddzielnych Q-types i specjalnego bajtkodów v, co gwarantuje, że adopcja klas wartościowych nie skutkuje drastycznymi zmianami w całym systemie ani masowymi rekompilacjami. Tekst new/dup/init: The Dance Goes On Johna Rose, który ukazał się w zeszłym tygodniu, opisuje zaś szczegóły, jak zaproponowane uproszczenie afektuje tworzenie obiektów
Podsumowując: W projektowaniu Java Virtual Machine (JVM) preferowany jest jasny i prosty model. Dodawanie skomplikowanych elementów, takich jak Q-types i v-bytecode, komplikuje cały system, sprawiając że jest trudniejszy w utrzymaniu. Nowa iteracja Valhalli upraszcza design i redukuje przyszły dług techniczny dzięki uniknięcia niepotrzebnej duplikacji afektujących cały system.
Zainstaluj teraz i czytaj tylko dobre teksty!
3. Project Hermes – nowy protokół przesyłu danych sieciowych
A na koniec coś wychodzącego poza JDK, ale pozostając w świecie niskopoziomowych komponentów.
Termin Big Data przez lata przeszły znaczącą ewolucję od czasu swojego początku opartego na modelu programowania MapReduce dwa dekady temu. Dzisiaj wielu wróży mu śmierć, ale wydaje mi się, że raczej specjaliści w tym zakresie nie mają się czego obawiać w najbliższym czasie, mimo tego, że świat regularnie znajduje sobie nowe obiekty pożądania.
Dlaczego w ogóle zresztą o tym temacie wspominam? Ponieważ wiele rozwiązań z dziedziny big data jest rozwijana w Java. Takie systemy są zwykle projektowane do działania na wielu serwerach w chmurach obliczeniowych, gdzie skalowanie poprzez dodawanie jednostek jest bardziej opłacalne niż zakup drogich superkomputerów – a do tego Java sprawdza się bardzo dobrze. Tak zwane skalowanie horyzontalne ma jednak swoje problemy – operacje jak reduce, aggregate czy join zwykle wymagają intensywnego transferu danych przez sieć, co często czyni ją wąskim gardłem – fizyka i Fallacies of Distributed Computing, mimo upływu lat od ich publikacji, pozostają w mocy. I o ile dostawcy chmur oferują obecnie ultranowoczesne sieci InfiniBand, które osiągają przepustowość nawet do 200 Gbit/s z minimalnym opóźnieniem. Jednakże model sieciowy Javy, oparte na gniazdach TCP/UDP, wprowadzają dodatkowy narzut podczas korzystania ze wspomnianego InfiniBand. Co prawda biblioteki takie jak JSOR czy jVerbs rozwiązują ten problem, dając Javie bezpośredni dostęp do InfiniBand, ale wymagają one modyfikacji aplikacji i są oparte na Java Native Interface (JNI).
Tutaj do gry wchodzi Projekt Hermes, który ma na celu unowocześnić obsługę sieci w Javie, oferując rozwiązanie komunikacyjne o ultrawysokiej prędkości. Inicjatywa ta opiera się o Projekt Panama i znane Wam pewnie Foreign Function & Memory API, które w końcu zastąpić ma podatne na błędy JNI. Hermes wprowadza dwie biblioteki open-source: Infinileap, która daje Javie bezpośredni interfejs do natywnej biblioteki UCX, obsługującej różne sieci, oraz hadroNIO, która oferuje wsparcie Java NIO oparte na tej bibliotece. Po testach na infrastrukturze chmury Oracle (bo to właśnie ta firma jest jednym ze sponsorów projektu, mimo że ten nie jest raczej oficjalnym projektem JDK), Hermes zademonstrował w pierwszych efektach znaczące poprawy wydajności dla aplikacji opartych o Netty’ego. W planach na przyszłość jest przeprowadzenie bardziej kompleksowych testów skalowalności oraz wprowadzenie niezależnego API dla Infinileap dla różnych języków programowania, wykorzystującego wzorzec Sidecar.
Jest to kolejna rzecz, która co prawda nie jest widoczna na pierwszy rzut oka, ale ułatwi wykorzystanie Javy w najbardziej wymagających workloadach. Jest to taki typowy puzzel, ale ważny dla całej układanki. Jak to w projektach akademickich, ukazał się whitepaper Accelerating netty-based applications through transparent InfiniBand support, w którym znajdziecie więcej szczegółów.
I dalej propsuje nazywanie projektów w oparciu o motywy mitologiczne, zwłaszcza kiedy dzieje się to w tak sprytny sposób.
Na zakończenie pragnę podzielić się z Wami pewną rekomendacją, która jest dla mnie szczególnie osobista. Jeden z pierwszych artykułów, które miałem okazję opublikować, nosił tytuł We tried Groovy EE — and what we have learned from it. W nim podzieliłem się doświadczeniami związanymi z użyciem Groovy w kontekście aplikacji Java EE. Pamiętam, że poznałem ten język dzięki książce Making Java Groovy autorstwa Kena Kousena. Z tego powodu z ogromną przyjemnością polecam Wam jego Newsletter, który jest dostępny na Substacku pod nazwą Tales from the jar side.
Stanowi on znakomite źródło wiedzy nie tylko na temat Javy (która jest prezentowana w nieco bardziej ogólnym kontekście, niż tylko techniczne aspekty JVM i JEP), ale również głębokich analiz oraz innych treści, które Ken poleca. Warto dodać, że każda edycja newslettera ma również swoją wersję wideo oraz zabawnych znalezisk z sieci – jeśli lubicie humor tego newslettera, jestem pewien, że materiały Kena też się Wam spodobają.
Dlatego też serdecznie zachęcam do zapoznania się z Tales from the jar side 😀