© drumdredd777/Shutterstock.com
Im Fokus: Projekt Valhalla

„Codes like a class, works like a primitive“


Projekt Valhalla [1] ist ein sehr umfangreiches Projekt, das Oracle bereits im Jahr 2014 gestartet hat. Aktuell gibt es bereits den zweiten Prototyp, am dritten wird gearbeitet. In diesem Artikel erläutern wir die Beweggründe hinter dem Projekt sowie Lösungsansätze und den aktuellen Stand.

Ziel des Projekts Valhalla ist die Bereitstellung einer JVM-Infrastruktur für die Arbeit mit unveränderlichen Objekten, die sich nur durch den Zustand ihrer Eigenschaften unterscheiden. „Codes like a class, works like a primitive“, wie es Brian Goetz, Java Language Architect bei Oracle, sagt.

In Java gibt es sowohl primitive Typen als auch Referenztypen. Das Speichern von Objekten im Java Heap hat seinen Preis. Die Metadaten des Objekts benötigen den zusätzlichen Speicher, z. B. für die Informationen, die für die Synchronisation von Objekten, Objektidentität, Polymorphismus und Garbage Collection benötigt werden. Allein das Speichern dieser Metadaten kann mehr Speicherplatz verbrauchen als die eigentlichen Eigenschaften des Objekts. Außerdem hat sich Hardware in den letzten Jahren massiv verändert. Wir haben ausschließlich mit Multikernprozessoren zu tun. Dabei haben sich die Kosten von sogenannten Cache Misses erhöht. Im Fall von Cache Miss wird der Wert nicht aus einem der Prozessorcaches, sondern aus dem Hauptspeicher geholt. Um das zu veranschaulichen, werfen wir einen Blick auf die Hardwarearchitekturen. Abbildung 1 zeigt eine sehr einfache Darstellung der Memory-Hierarchie. Wir sehen, dass jeder Prozessor eigene L1-(First Level-) und L2-(Second Level-)Caches besitzt. Es gibt außerdem noch einen optionalen, prozessorübergreifenden L3-Cache.

alukhanov_kazulkin_peters_valhalla_1.tif_fmt1.jpgAbb. 1: Memory-Hierarchie

In den Caches werden die Informationen gespeichert, die der Prozessor wahrscheinlich sehr häufig und in naher Zukunft für seine Berechnungen benötigt. Bei der Suche nach Informationen geht der Prozessor die Caches in der Reihenfolge L1, L2, L3 nacheinander durch. Falls in keinem der Caches die vom Prozessor gesuchte Information zu finden ist (Cache Miss), findet ein Zugriff auf den Hauptspeicher statt. Generell gilt: Je näher der Cache am Prozessor ist, desto kleiner ist er. Wie groß die Caches tatsächlich sind, kann man unter Linux mithilfe von lscpu ermitteln – für Windows empfiehlt sich die Nutzung des Tools CPU-Z. Normalerweise ist der L1-Cache ab 64 KB (32 KB jeweils für Instruction und Data Cache), der L2-Cache ab 256 KB und der optionale L3-Cache ab 2 MB groß. Einen sehr guten Überblick über die Arbeitsweise der Caches liefert der Artikel von Joel Hruska [2]. An dieser Stelle sind noch die Zugriffszahlen wichtig: Je näher am Prozessor, desto schneller ist der Zugriff. Diese Zugriffszeiten können wir z. B. auf der Seite „Latenzen, die jeder Entwickler wissen soll“ [3] ermitteln. Wir vergleichen jetzt die Zahlen für das Jahr 1996 (Abb. 2) – Geburt der Programmiersprache Java – und 2020 (Abb. 3).

alukhanov_kazulkin_peters_valhalla_2.tif_fmt1.jpgAbb. 2: Zugriffszeiten auf L-Caches und RAM im Jahre 1996
alukhanov_kazulkin_peters_valhalla_3.tif_fmt1.jpgAbb. 3: Zugriffszeiten auf L-Caches und RAM im Jahre 2020

Wie wir in den beiden Abbildungen zu den Zugriffszeiten sehen, hat im Jahr 1996 der Zugriff auf den Hauptspeicher im Vergleich zum L1-Cache fast das Sechsfache gekostet (134 ns vs. 23 ns). Der gleiche Zugriff kostet im Jahre 2020 schon das Hundertfache (100 ns vs. 1 ns). Es ist ein enormer Performancegewinn, wenn die Werte, auf die man häufig zugreift, näher am Prozessor gespeichert werden. Auf dem Hauptspeicher wird nicht auf die einzelnen Cacheeinträge zugegriffen, sondern aus Performancegründen auf die ganzen Cache-Lines (normalerweise 64 Bytes groß), wo sich mehrere Cacheeinträge befinden.

Jetzt aber zurück zu den Referenztypen in Java. Dabei haben wir auch mit solchen zu tun, die unveränderlich sind und sich nur durch den Zustand ihrer Eigenschaften unterscheiden. Die besten Beispiele aus der täglichen Nutzung der Standard-Java-Bibliotheken sind die Klassen LocalDateTime und Optional. Solche Objekte benötigen per Definition keine Information für die Synchronisation von Objekten, Objektidentität und Polymorphismus, da sie unveränderbar sind. Den Garbage Collector kann man für solche Objekte ebenfalls optimieren. Auch im Hinblick auf die Zugriffszeiten ist bei den genannten Referenztypen ein enormes Optimierungspotenzial vorhanden.

Es ist an dieser Stelle denkbar, statt klassischer Referenzobjekte eine Datenstruktur einzusetzen, die ähnliche Objekteigenschaften besitzt, nicht aber den genannten Overhead hat.

Inline Types

Hauptfokus des Projekts Valhalla ist die Erweiterung der Java-Sprache um einen neuen Datentyp namens Inline Type (ursprünglich Value Type genannt), der einige wichtige Eigenschaften der Referenztypen wie Konstruktoren, Methoden und Felder inklusive Sichtbarkeitsmodifikatoren beibehält, sich aber in einigen Merkmalen unterscheidet. Dabei sollen sich die Inline Types an das Laufzeitverhalten primitiver Typen anpassen.

Bei Inline Types handelt es sich um die Datenstrukturen, deren Werte statt einer Referenz direkt im Speicher abgelegt werden. Dabei wird genau so viel Speicher belegt, wie es für die in einzelnen Feldern enthaltenen Daten nötig ist. Speicherverbrauch für Metadaten entfällt komplett. Man kann das mit primitiven Datentypen vergleichen, bei denen ein int genau 32 Bit verbraucht.

Das direkte Speichern der Daten statt ihrer Referenzen wird ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang