Der Weg nach Valhalla

(Kommentare: 0)

Valhalla? Die Wohnung der Gefallenen auf Odins Burg in Asgard? Beschäftigt sich SyroCon denn nun mit nordischer Mythologie?

Nein, auch bei unserem breit aufgestellten Beratungsumfeld zählt das nicht zu unserem Tätigkeitsbereich – wohl aber interessiert uns alles rund um die Java-Plattform und mich speziell etwas, das sich Project Valhalla nennt und durchaus als episch bezeichnet werden kann. Das liegt nicht nur an der Projektdauer des 2014 gestarteten Vorhabens ohne versprochenem Endtermin („it’s ready when it’s ready“), sondern auch an den Auswirkungen auf die JVM und deren Spezifikationen. Es handelt sich dabei um einen Inkubationsbereich für experimentelle Java Sprach-Features – unter anderem für die sogenannten Value Types.

Codes like a class, works like an int

Value Types sind eine Art von Datentypen in Programmiersprachen, die ohne Referenz auf einen Speicherbereich im Heap auskommen sondern direkt als Wert auf dem Stack benutzt werden können, zum Beispiel der Zahlwerttyp int. In Java heißen diese auch „primitive Datentypen“ und werden von den Reference Types, also den Objekten, unterschieden. Letztere können nicht nur unterschiedliche Daten in beliebigen Strukturen beinhalten, sondern in aller Regel auch deren Verarbeitung steuern und damit Zustand und Verhalten kapseln.

Nun gibt es aber viele Situationen, in denen tatsächlich nur Datencontainer ohne spezielles Verhalten benötigt werden, also so etwas wie eine Data Class. Kotlin beispielsweise kennt dieses Konstrukt und bietet eine data class an, die Utility-Funktionen bereitstellt und Getter/Setter überflüssig macht. Allerdings kann Kotlin als JVM-Sprache auch nur genau das tun, was die JVM erlaubt. Die dortigen Datenklassen sind eben immer noch Objekte die dem Entwickler lediglich den Umgang damit erleichtern, sich auf unterer Ebene aber nicht von anderen Objekten unterscheiden.

Für reine Datenklassen müsste man eine neue Art von Datentyp schaffen, die Eigenschaften von primitiven und objektorientierten Datentypen vereinen – nach dem Motto „codes like a class, works like an int“. Was wären die Eigenschaften eines solchen Value Types?

  • Identity-less: Genau wie ein int 42 sich nicht von einem anderen int 42 unterscheiden muss, bräuchten auch Value Types keine eigene Identität wie ein Objekt – eine Prüfung auf Gleichheit beruht einzig auf dem State.
  • Immutable: Value Types könnten unveränderbar sein, so wie das beispielsweise bei String der Fall ist: Eine Änderung an einer Instanz führt zur Erstellung einer neuen Instanz.
  • Non-synchronized: Der Zugriff auf Value Types müsste nicht synchronisiert werden, da sich der Zustand nicht ändern kann.

Reboot layout of data in memory

Die Kombination der genannten Eigenschaften würde eine Reihe von Optimierungen im Data Layout und bei der Verarbeitung ermöglichen:

  • Flattening: Die enthaltenen Daten eines Value Types könnten in einer flachen Datenstruktur, beispielsweise einem Array, statt mittels Pointer verbundener Strukturen gespeichert werden, was den Footprint eines solchen Datentyps merklich reduzieren würde.
  • No indirection: Durch die fehlenden Pointer entfallen Indirektionen beim Zugriff, die nicht nur zusätzliche Speicherzugriffe bei der Nutzung herkömmlicher Objekte bedeuten, sondern auch Cache Misses und das Laden von weiteren Memory Pages nach sich ziehen können.
  • No GC: Value Types bräuchten keine komplexe Behandlung seitens des Garbage Collectors, da sie immer als Ganzes entfernt werden könnten, was die Last auf den GC reduzieren würde.
  • Pass-by-value semantic: In Java gibt es ausschließlich pass-by-value, da Referenzen nicht wie beispielsweise in C++ geändert werden können. Jedoch würde diese Semantik für das ganze Datenobjekt gelten, das wie ein primitiver Datenwert weitergereicht werden könnte.
  • Stack usage: Die Speicherung kann auf dem Stack oder in Registern erfolgen, ohne Nutzung des Heaps. Das ist nicht nur schneller, sondern entfernt auch das gesamte Datenobjekt durch eine Pop-Operation, sobald es out-of-scope geht.

Nicht nur das Data Layout wäre dadurch massiv vereinfacht, sondern die JVM könnte beim Just-in-time-Compile auch viel aggressiver optimieren, da im Gegensatz zu herkömmlichen Objekten viele Situationen nicht mehr auftreten können, die eine Optimierung verhindern. Ein Beispiel dafür ist die relativ teure Escape Analysis, bei der der JIT-Compiler prüft, ob gerade erzeugte Objekte in den Heap „verschwinden“ können. Im Falle der Value Types wäre das nicht mehr möglich und somit immer das sehr wirkungsvolle Instrument des Scalar Replacements einsetzbar, bei dem das Objekt auf dem Heap durch lokale Datentypen seiner Bestandteile auf dem Stack ersetzt werden kann.

Zur Veranschaulichung ein Beispiel: Ein Punkt in einem zweidimensionalen Koordinatensystem wird als Tupel von Zahlwerten modelliert – einmal herkömmlich (links) und einmal als Value Type (rechts):

Davon werden jeweils mehrere Punkte in einer Array-Struktur gespeichert:

Das führt zu einer durch Referenzen verbundenen Objektstruktur für das herkömmliche Objekt (links) und zu einer flachen Datenstruktur für den Value Type (rechts):

Nutzt man nun eine solche Datenstruktur in einem rechen- und speicherintensiven Algorithmus, zum Beispiel der Matrizenmultiplikation von komplexen Zahlen, kommt man auf einen erstaunlichen Faktor 10 bei der Ausführungszeit und sogar 1000 bei der Speicherallokation!

The L-World

Das Problem mit neuen Sprachfeatures in Java ist zum einen das unantastbare Mantra der Rückwärtskompatibilität, damit kein existierender Code gebrochen wird, sowie die Tatsache, dass dadurch solche Features für immer gelten müssen. Zum anderen ist eine Änderung an der Basis der VM eine heikle Angelegenheit, die Auswirkung auf die ganze Sprache und sämtliche APIs hat. So gibt es beispielsweise (Stand heute) noch keine Möglichkeiten, Generics auf primitive Datentypen zu spezialisieren, was bei der Verwendung von Value Types notwendig wäre. Aus diesem Grund ist das Sprachfeature Generic Specialization auch ein Teil des Valhalla-Projekts.

Man hat sich daher entschieden, eine iterative prototypische Implementierung vorzunehmen, bei der man ausgehend von einem Minimal Value Types Prototype (MVT), das Feature in einer Serie darauf aufbauender Prototypen (LW-X) mit dem vorhandenen Typsystem zusammenbringt. Die existierenden Typen werden auch als „L-Types“ bezeichnet, weshalb das ganze System den Namen „L-World“ bekommen hat. Von dieser L-World wird es dann durch die iterativen Implementierungen zahlreiche Versionen geben, die nach und nach mit dem Mainline-JDK und den vorhandenen Klassen integriert werden können.

Bis das Feature dann komplett in einer JDK-Version verfügbar ist, werden noch einige Major-Versionen von Java ins Land gehen. Man muss also Geduld haben und gewinnt durch diesen Blogbeitrag vielleicht ein Verständnis dafür, warum es so lange dauert – ich in jedem Fall freue mich schon darauf!

 

Alex Rühl

Zurück