Project Panama - the JNI Replacement (slidedeck)

Heyho,

ich habe letzte Woche auf der JavaLand einen Vortrag über Project Panama gehalten. Panama soll auf Dauer die meisten Usecases von JNI ablösen und eine Art echtes Foreign Function Interface (wie es andere Sprachen haben) nach Java bringen. Generell, vereinfachter Übergang Java -> Native Code.

Zusätzlich sind weitere Features Teil des Proposals, wie z.B. Zugriff auf Vector Funktionen der CPU direkt aus Java heraus.

Das Slidedeck steht wie immer auf Slideshare in meinem Account zur Verfügung.

Als Timeline für Project Panama gab es die Aussage “definitiv vermutlich Java 10 oder später” (ja ich mag den Satz ;-)). Ergo noch ein wenig Zeit.

Mich würde interessieren was ihr (besonders die Leute, welche native Libs in Java integrieren) von den aktuell vorgestellten APIs halten (welche Teils auch noch extrem early drafts sind) und vor allem welche Usecases euch spontan einfallen. Abgesehen davon, schaut euch vor allem auch die Links / Videos auf der vorletzten Seite an um weitere Informationen zu erhalten.

Chris

Irgendwie fühle ich mich angesprochen :wink:

Aber es ist schwer, etwas fundiertes dazu zu sagen. Ich bin zwar schon lange z.B. bei der Panama Mailing List registriert, muss aber zugeben: Es ist ((für mich) zu) viel. Ich habe gerade mal in meinen Mails runtergescrollt zum relevante(st)en markierten/ungelesenen Beitrag, und der war notes on binding C++ . Allen relevanten Projekten tatsächlich in aller Tiefe zu folgen, das nachzuvollziehen und sogar zu bewerten oder etwas beizutragen könnte beliebig viel Zeit in Anspruch nehmen. Deswegen würde ich eher einen Vortrag auf einer Konferenz hören wollen, wo mal innerhalb kurzer Zeit ein kompakter Wrap-Up über den letzten Stand, ohne viel Gelaber gemacht wird…

Die großen (und teilweise recht nah verwandten) Punkte sind ja wohl

Bei den meisten hatte ich schon einiges gelesen (z.B. die frühen proposals zu den Value Types auf Value Types for Java ), aber auf dem neuesten Stand in bezug der Antworten auf die dort aufgeworfenen Fragen bin ich nicht.

Inwieweit die Projekte sich untereinander koordinieren oder abstimmen, ist schwer zu sagen. Aber bei der personellen Überschneidung, die es da gibt, sollte das wohl selbstverständlich (bis hin zu „nicht nötig“) sein :smiley: .

Etwas aus der Reihe fällt Sumatra oben schon: Das passiert im Hotspot JIT, und hat nichts mit einer API zu tun. Aber der Grund, warum ich es hinzugefügt habe, sollte klar sein: Das Ziel von JCuda und JOCL ist gerade, die existierenden GPU-Libraries per JNI nutzbar zu machen. In diesem Sinne kann man die Ziele der anderen beiden Projekte einordnen.


#Foreign Function Interface

Dass zum Ansprechen der existierenden Libraries etliche Megabytes (!) JNI-Code notwendig sind, grenzt ans absurde. Viel von dem Code ist automatisch generiert, und das ist der Punkt: Er kann automatisch generiert werden! In den allermeisten Fällen wird dort nichts weiter gemacht, als die Java-Parameter (int, float…) über die JNI-Schicht (jint, jfloat…) an die C/C+±Funktion (int32_t, float…) weiterzureichen.

Spannender wird es natürlich bei nicht-primitiven Typen. Auch da kann die Übersetzung oft weitgehend automatisch gemacht werden - etwa bei einfachen structs - aber auch da gibt es schon Caveats, wie etwa Alignment und Padding. Wenn ich sehe, wie das teilweise gehandhabt wird (etwa bei LWJGL oder JavaCPP) schaudert es mir. (Das ist nicht wertend!!! Es gibt einfach keine andere (generische!) Möglichkeit!). Aber richtig schwierig wird es bei komplexeren Strukturen, wie echten (ggf. virtuellen) Klassen, oder Function Pointers…

Kurz: Ein echtes, generisches FFI ist schwierig, weil bestimmte Konstrukte nur schwer sprachübergreifend übersetzbar sind.

Interessanterweise gab es in den ersten Versionen der JNI-Doku ein Kapitel dazu. Das Kapitel wurde irgendwann weggelassen, und ist darum hier nur über das WebArchive verlinkt: Leveraging Existing Native Libraries Dort steht, wie man so ein generisches Interface basteln kann … mit einigem an magischem Inline-Assembler :fearful:

(Ich werde wohl früher oder später mal auf stackoverflow fragen, warum das weggelassen wurde. Die Frage wäre ein Kandidat für Downvotes und Close Requests, aber … manchmal antworten dort auch Leute wie Rose und Goetz, vielleicht könnten die etwas Einsicht bringen… :thinking: )


#Vector Operations

Diese beziehen sich, soweit ich das bisher verstanden habe, hauptsächlich auf Instruction-Level-Parallelism. (Aber muss zugeben, da vieles bisher noch nicht in der nötigen Tiefe gelesen zu haben). Der Grund, weswegen ich da eine Parallele zu den anderen Themen sehe ist, etwas oberflächlich gespochen: Die GPU bietet genau solche Vector Operations an! Nicht zuletzt deswegen hatte ich irgendwann mal GitHub - jcuda/jcuda-vec: Vector operations for JCuda erstellt. Wenn sowas in Zukunft ohne JCuda, CUDA und die JNI-Schicht möglich wäre, und von der JVM direkt unterstützt würde, und damit direkt von der JVM entweder auf der CPU oder der GPU ausgeführt würde, wäre das super.

(Darüber schwebt wie ein Damoklesschwert die Frage nach dem Speicherbereich: Wenn es auf der GPU ausgeführt werden soll, müssen die Daten im Device Memory liegen. Wenn der JIT die Daten dann wild zwischen GPU und CPU hin-und-her kopieren müsste, würde das nicht sooo viel bringen :wink: Aber es gibt mit dem „Unified Memory“ oder „Mapped Memory“, und dem, was die HSA-Foundation macht, einige Hoffnung, dass schon sehr bald die GPU und die CPU auf den selben Speicher zugreifen können, von daher ist dieses Damoklessschwert vielleicht gar nicht so spitz und bedrohlich, wie es im ersten Moment aussieht).


#Value Types

Ein sehr wichtiger und interessanter Punkt. An verschiedenen Stellen musste ich schon erklären, warum die Leute ihren

class Point {
    float x,y,z;
}
Point points[] = new Point[100];

array eben nicht so auf die GPU rüberkopieren und dort als

struct Point {
    float x,y,z;
}
Point points[100];

array verwenden können. (Dabei sieht der Code doch sooo ähnlich aus!). Den Grund hast du ja auch in den Folien beschrieben: Die Point-Objekte liegen in Java potentiell im ganzen Heap verteilt. Um sowas „gut“ auf die C+±Seite mappen zu können, muss man sicherstellen können, dass man auch in Java einen einzigen, verläßlichen Memory-Block hat, dessen Layout genau mit dem auf der C-Seite übereinstimmt (und der idealerweise nicht beliebig vom GC in der Gegend rumbewegt werden kann - aber das ist ein weiterer, so gesehen unabhängiger Punkt).


Nochmal: Der aktuelle Stand der Ansätze (z.B. das mit den Layout-Klassen, die in den Folien angedeutet sind), kenne ich nicht - und noch weniger die interne Umsetzung. Aber das sind alles Punkte, die, wenn sie vernünftig umgesetzt sind und schön zusammenspielen (!) etliche neue, interessante Möglichkeiten bieten könnten (und vielleicht auch JCuda und JOCL überflüssig machen, aber wenn damit alles „besser und einfacher“ wird, wäre das schon OK :wink: )

1 „Gefällt mir“

Ui, nun das ist eine lange Antwort :smiley:

Warum auch immer :wink:

Zum Projekt Sumatra, ich glaube das ist nahezu tot, wird aber durch Panama durchaus mit in Angriff genommen (ka ob das fester Bestandteil geworden ist oder parallel dazu entwickelt wird, aber die Integration wird ziemlich sicher kommen).

Ich denke, dass der Abstraktionslevel ausreichen sollte um entsprechend den Code auf entweder CPU oder GPU auszuführen, sofern die Runtime versteht wie sie z.B. CUDA Binary erzeugen kann / muss.

Schöner hätte ich es nicht sagen können :wink:

Zum Thema Vector Operationen: Derzeit wird es auf die SIMD Opcodes (also SSE/SSE2/AVX) gemapped. Trotzdem denke ich auch hier, wird es der Runtime egal sein, wo es ausgeführt werden soll, solange sie binaries für die Zielumgebung erstellen kann. Mit etwas Verstand könnte sie vllt sogar rausfinden ob einfache oder doppelte Genauigkeit und dann selber entscheiden wohin. Von wegen Memory Access, jo entweder vorher umkopieren (auch hier könnte ein Threshold einen Ansatz geben, ab wann der Overhead für das Kopieren unterliegt gegenüber Geschwindigkeitszuwachs) oder eben sowas wie HSA und ich glaube das wird in Zukunft wichtiger werden. Unified Memory Addresses in einer Form oder der anderen wird kommen.

Value Types, jo genau. Nicht nur zur Übertragung nach C++ oder so ist es wichtig, sondern auch für Column-based operations. Also schnelle Array-Operationen. Mal ganz abgesehen von Pointer-Chasing und Cache-Lines :slight_smile:

Dass Sumatra „tot“ zu sein scheint, habe ich auch bemerkt. Ich hoffe (oder gehe davon aus), dass das Projekt auch eine Art „Sandkasten“ war, in dem sich die Verantwortlichen mit den Konzepten und Anforderungen von GPUs vertraut gemacht haben, um diese dann konsolidiert in die anderen Projekte einfließen zu lassen.

Nochmal: Eigentlich haben die Projekte nichts miteinander zu tun. Das eine ist rein Hotspot-Internal, die anderen beziehen sich auf Dinge, die öffentlich sein sollen. Aber zumindest wird Sumatra in diesem Sinne ein „tracing bullet“ gewesen sein, im Sinne von „Was genau soll wie angeboten werden, damit man später möglichst viele Möglichkeiten hat?“

Diese Möglichkeiten, speziell in bezug auf die Vektorisierung:

Ja, bei der geeigneten Abstraktion wird das hoffentlich möglich und transparent für den Entwickler. Gerade in Kombination mit dem JIT könnte es da einiges an Potential geben. Bei sowas wie

float x[] = ...;
float y[] = ...;
float result[] = ...;
Vectors.add(x,y,result);
System.out.println(result[42]);

könnte der JIT erkennen: Da wird nur eine Operation gemacht. Da lohnt es sich nicht, den Speicher umzukopieren. Bei sowas wie

float x[] = ...;
float y[] = ...;
float result[] = ...;
Vectors.sin(x,x);
Vectors.cos(x,x);
Vectors.tan(x,x);
Vectors.sqrt(x,x);
Vectors.sin(y,y);
Vectors.cos(y,y);
Vectors.tan(y,y);
Vectors.sqrt(y,y);
Vectors.sin(x,x);
Vectors.add(x,y,result);
System.out.println(result[42]);

könnte er erkennen:

  • Die Arrays sind groß
  • Es gibt viel komplexe Trignonometrie
  • ( → Compute Bound, viele Instruktionen pro Speichereinheit!)
  • Ich habe eine GPU an Bord
  • Der Speicher wird zwischen den Operationen nicht auf der Host-Seite benötigt

… da könnte es sich lohnen, das rüberzukopieren.

Sicher, das für den allgemeinen Fall im JIT zu Implementieren ist unglaublich kompliziert (der skizzierte Fall ist betont (lächerlich) trivial), aber … am JIT arbeiten einige recht schlaue Köpfe, und die haben schon ganz anderen shyce hingekriegt, bei dem man sich naiv denken würde: Wie konnten sie das denn rausfinden und optimieren? :astonished:

1 „Gefällt mir“

Relativ ähnlich wurde es in Sumatra ja gemacht. Da war nur sehr einfaches Pattern matching drin aber tatsächlich haben sie bestimmte Muster schon erkannt gehabt und den passenden Kernel automatisch implementiert und gestartet.

Um hier mal kurz ein neueres Deck zum Thema Vectorization nachzulegen: http://cr.openjdk.java.net/~vlivanov/talks/2017_Vectorization_in_HotSpot_JVM.pdf

Denke ist gerade für dich interessant @Marco13

Es ist (abhängig vom Stil der Folien und anderen Faktoren unterschiedlich) schwierig, NUR aus Folien Botschaften rauszuziehen.

Aber wenn ich die Information von Slide 40ff richtig verstehe, kann der JIT bei sowas wie

int [] A, B, C; 
for (int i = 0; i < MAX; i++) { 
    A** = B** + C**; 
} 

ain automatisches Unrolling machen, und das Ergebnis dann vektorisieren, während er das nicht kann, wenn man das ganze schon manuell zu

for (int i = 0; i < MAX-4; i+=4) { 
    // main loop
    A[l+0] = B[l+0] + C[l+0]; 
    A[l+1] = B[l+1] + C[l+1]; 
    A[l+2] = B[l+2] + C[l+2]; 
    A[l+3] = B[l+3] + C[l+3]; 
} 

ge un-rolled hat. Und das ist doch mal interessant (d.h. relevant) : Manuelles Loop-Unrolling ist demnach kontrproduktiv.

Diese Einsicht ist (falls richtig :wink: ) so gesehen nicht wirklich „neu“. Man sollte sich darüber im Klaren sein, dass der JIT ein Biest ist, und besser weiß, was er wie zu optimieren hat. (Und so gesehen ist das nur eine Ausprägung des allgemeineren Statements Write Dumb Code, das Brian Goetz schon vor vielen Jahren gemacht hat - im Kern mit genau dieser Begründung!)

Ansonsten … die Teile, die nur aus „Code → Assembler“-Folien bestehen sind auf die Schnelle nicht nachvollziehbar, gehen aber wohl auch sehr weit ins Detail.


Dass er mal klipp und klar sagt:

Unsafe == Fast? BUSTED!

finde ich gut. Leute haben in den absurdesten Konstellationen und für die Absurdesten Zwecke Unsafe verwendet, und meistens wohl mit dieser (FALSCHEN) „Unsafe==Fast“-Begründung. Hoffentlich trägt das dazu bei, dass Leute sich in Zukunft genauer überlegen, ob sie das wirklich verwenden wollen…


Das Hydra-Problem: Die Folien haben bewirkt, dass etliche Links (z.B. Minimal Value Types (Shady Edition) ) in meinem THINGS_TO_READ-Ordner gelandet sind. Wenn ich mehr involviert wäre, würde ich mir das ganze mal ansehen und ggf. ausprobieren, und/oder sogar fragen ob/wie man diese Vectors auch auf die GPU bringen könnte/wird (idealistisch: … oder es mal ausprobieren :pensive: und sei es nur als POC, als Tribut an Sumatra…)

1 „Gefällt mir“