# Performance Optimization Strategy and Developer Community Marketing

**Podcast:** Engineering Kiosk
**Published:** 2026-04-28

## Transcript

Willkommen zu einer neuen Episode vom Engineering Kiosk Podcast.
Heute wird es schnell, richtig schnell.
Wir sprechen über eine Challenge, die auf den ersten Blick fast harmlos klingt und dann völlig eskaliert.
Die One Billion Road Challenge.
Eine Milliarde Zeilen Wetterdaten einlesen, pro Station Minimum, Mittelwert und Maximum berechnen und das Ganze so schnell wie möglich.
Klingt nach einer simplen Fleißarbeit?
Tja, genau hier wird es spannend.
Denn wir schauen uns auch an, Wie aus einer naiven Java-Implementierung mit fast 5 Minuten Laufzeit plötzlich Lösungen mit rund 1,5 Sekunden werden.
Und dabei geht es nicht nur um Java, DJVM und GralVM, sondern auch um die Frage, was Performance-Optimierung eigentlich wirklich bedeutet.
Lokale Hashmaps, Memory Mapping, Integer statt Float, Single Instructions Multiple Data, kurz SIMD, Branchless Coding, Unsafe, Cache Lines und warum plötzlich sogar das Parsen eines Temperaturwerts zur Wissenschaft wird.
Außerdem sprechen wir darüber, warum diese Challenge so viral gegangen ist, welche Kritik es daran gab und was andere Sprachen wie C, Go, PHP, SQL oder sogar AWK daraus gemacht haben und warum eine GPU hier nicht automatisch der Endgegner ist.
Wenn du wissen willst, ob Java wirklich langsam ist oder ob wir diesem Mythos heute endgültig den Checker ziehen, dann bleib dran.
Los geht's!
Lieber Wolfgang, die Grüße gehen raus an das wunderschönste Land mit dem leckersten Kaiserschmarrn.
Ohne Rosinen.
Und heute haben wir mal ein Thema, was ich schon lange auf der Uhr habe, aber wir starten mit einer einfachen Frage für dich.
Was war die letzte Performance-Optimierung, die du geschrieben hast oder gepromptet hast?
Moment, ich war kurz abgelenkt.
Bei Kaiserschmarrn habe ich schon abgeschalten, weil du hast von einem Land gesprochen, wo es Kaiserschmarrn ohne Rosinen gibt und der der Beste ist.
In welchem Land ist es das?
Weil in Österreich gibt es sowas nicht.
In Österreich gibt es sowas vermehrt.
Da gibt es nur mit Rosinen.
Glaubenskrieg.
Bin ja nicht religiös, deswegen halte ich mich von solchen Kriegen fern.
Du wolltest jetzt Einleitung machen in religiöse Kriege in der Entwicklerwelt, wo es darum geht, ob man Micro-Optimization machen soll oder nicht.
So, jetzt hör auf, die Frage wegzuschmeißen, so wie der Berater es macht.
Komm mal raus aus deinem Beruf und komm mal wieder rein in den Podcast, wo du mal essentiellen Wert liefern sollst und unter anderem Fragen beantworten sollst, die dir gestellt werden.
Was war die letzte Performance-Optimierung, die du geschrieben hast?
Oder gepromptet hast?
Ich glaube, meine Optimierungen bewegen sich eigentlich immer auf der SQL-Ebene.
Also wenn ich so zurückdenke in den letzten Monaten, ich weiß, dass ich sicher mal mindestens 20 SQL-Queries optimiert habe.
Weil im Code habe ich eigentlich sehr selten Optimierungen, muss ich sagen.
Im Code hast du selten Optimierungen oder im Code hast du selten die Bottlenecks?
Beides.
Und dadurch habe ich auch keine Optimierungen, weil ich selten unterwegs bin, wo man mit wirklich sehr großen Daten hantiert.
Und oft geht es ja um große Datenmengen.
Und wenn ich große Datenmengen habe, dann liegen die meistens in irgendeiner Datenbank und dann sind wir wieder beim SQL-Thema.
Das führt mich aber auch zu der Annahme, dass du in letzter Zeit auch keine compute-intensiven Applikationen geschrieben hast.
Wenn du mal von dem Compute in SQL-Queries absiehst, ja.
Ja, ja, genau, klar.
Also so zumindest nicht, dass ich jetzt an extreme Bottlenecks drangekommen wäre, wo ich mir so gedacht habe, ja, okay, dann ist die CPU-Load halt ein bisschen höher.
Statt drei Prozent ist sie vier Prozent.
Verstehe, verstehe.
Okay, und die nächste Frage, da möchte ich ein...
Eine Antwort, die aus der Hüfte geschossen kommt, so richtig Bauchgefühl.
Jetzt nicht drei Sekunden, vier Sekunden, fünf Sekunden überlegen, weil wir schneiden den Podcast und alle Leute am Audio-Gerät kriegen das nie mit, wenn du so lange überlegst.
Ja, darum bin ich immer super schnell, das ist ja genial.
Ja, ja, deswegen jetzt mal flott, flott, Bauchgefühl.
Was ist dein erster Gedanke, wenn du Java und Performance hast?
Um Gottes Willen.
Muss ich zugeben, jetzt mal ohne Cut hier, ohne das Audio geschnitten zu haben, die kamen sogar relativ schnell, ich würde sagen so eine, anderthalb Sekunden.
Interessant, was führt dich zu diesem Bauchgefühl, zu diesem ersten Gedanken?
Ja, grundsätzlich mal das Klischee, aber ich hatte einen Kollegen an der Uni, der an hochoptimierten Indexstrukturen gearbeitet hat und der hat mit Java angefangen, weil er ein großer Java-Fan war.
Und auf allen Konferenzen, wo er das eingereicht hat, obwohl sein Index sehr, sehr schnell war, hat es geheißen, warum machst du es in Java?
Warum kommst du mit Java um die Ecke?
Um Gottes Willen, geh weg mit deinem Java.
Und am Ende hat er es dann in C implementiert, damit alle Leute glücklich waren.
Und man muss zugeben, er war dann auch schneller in C.
Aber gar nicht so viel schneller, wie man vielleicht denken würde.
Färbung.
Auch wenn Frachtwerk ein Telematiksystem für alle Baumaschinen der Deutschen Bahn in ihrem eigenen Cluster betreibt, handelt es sich hierbei nicht um ein Bahnunternehmen, sondern um ein IT-Unternehmen für Beratung und Softwareentwicklung.
Remote sowie an zwei Standorten in Berlin und in Karlsruhe arbeiten derzeit vier Entwickler- und Beraterteams an Software-, Transformations- und Data-Analytics-Projekten.
Zudem betreuen sie den automatisierten Betrieb von 300 Systemen im Zero-Trust-Modell mit über 1000 Containern für unterschiedliche Kunden, von der Logistik bis hin zu öffentlichen Kommunen.
Als New Work Organisation läuft bei Frachtwerk aber vieles etwas anders.
Zum Beispiel wird das Gehalt über ein New Pay Gremium aus gewählten Vertreterinnen bestimmt und fachliche sowie persönliche Weiterbildung ist tief in der Kultur verankert.
Diese Kultur umfasst auch Open Source, wobei es nicht nur verwendet, sondern auch gelebt wird.
Zum Beispiel ist das hauseigene Java Start Framework auf Basis von Springbrood unter der LGPL Lizenz auf GitHub verfügbar.
Schau dir doch einmal Frachtwerk genauer an, denn es wird Verstärkung gesucht.
Speziell SoftwareentwicklerInnen mit dem Schwerpunkt Java und DevOps EngineerInnen mit dem Schwerpunkt Kubernetes.
Alle Infos unter engineeringkios.dev slash frachtwerk oder in den Shownotes.
Ich glaube auch, dass immer nur Leute sagen, Java ist langsam oder Java und Performance, das sind so zwei gegenläufige Begriffe in einem Satz, dass das von Leuten kommt.
die wenig Ja war, schreiben.
Ich meine, man muss natürlich schon sagen, dass die JVM irgendwo da dazwischen liegt.
Das heißt, wenn du natürlich jetzt Hardcore-Optimierungen machen willst, dann musst du immer diese JVM mitdenken.
Was macht denn die JVM da im Hintergrund?
Was macht die vier Optimierungen?
Und in C kannst du halt einfach, da gibt es niemanden, der dich irgendwo da unterstützt bei den Optimierungen.
Da musst du halt alles selber machen, dafür kannst du auch alles selber machen.
Das sehe ich so als größten Unterschied, wobei die JVM natürlich auch, wenn man jetzt zurückblickt, Die letzten 20 Jahre, in denen Java sich natürlich sehr stark weiterentwickelt hat, hat sich die JVM auch weiterentwickelt und ist auch wesentlich optimierter geworden und hat auch mehr Möglichkeiten und werden wir heute auch bei...
Dinge besprechen.
Ja, ich bin mir da nicht sicher, ob ich da 100% mitgehe, dass man immer drüber nachdenken muss, was macht die JWM da für Optimierung, denn dann würde ja das gleiche Argument gelten, okay, bei C, was macht der Compiler für Optimierung, weil der Compiler optimiert ja auch Sachen weg und schreibt ja auch insgeheim mehr oder weniger deinen Code, wo er denkt, er wäre smarter und Co.
Ja, aber das musst du auch machen, weil ich kann mich selber erinnern, in den ganzen Optimierungs...
Beispielen und so, die wir gemacht haben, auch paralleles Rechnen und so weiter, damals an der Uni noch.
Ich hatte das sehr oft, am Ende war das Programm superschnell und dann bist du draufgekommen, der hat dir einfach eine komplette Loop wegoptimiert.
Und du wolltest das eigentlich zeigen, dass du das irgendwie schneller machen kannst.
Der Compiler O3 Flag hat einfach gecheckt, oh, da gibt es nie einen Output, der wird nie verwendet.
Ich kicke mal die ganze Loop einfach raus.
Ich meine, bei der ganzen Compiler-Optimierung Gab es natürlich auch etliche Bugs in der ganzen Historie, aber darum geht es heute nicht.
Denn es geht heute mal etwas darum, ob ich dich davon überzeugen kann, dass Java und Performance doch vielleicht in einem Satz genannt werden können.
Denn die heutige Episode dreht sich um ein Thema, was ich schon länger im Kopf habe.
Schon eigentlich fast zwei Jahre, aber ich kam noch nie dazu.
Und zwar sprechen wir heute über die One Billion Row Challenge.
Also zu Deutsch, weil es gibt auch das Wort Billionen in Deutsch.
Eine Billion in Englisch ist nicht eine Billionen in Deutsch.
Eine Billion in Englisch ist eine Milliarde in Deutsch.
Also reden wir heute über die eine Milliarden Zeilen Challenge.
Hast du davon schon mal gehört?
Von dir im Monatsdakt.
Das ist schön, dass du dich an unsere Kommunikation erinnerst.
Wenigstens etwas Positives hier.
Fangen wir an.
Und zwar gibt es einen Software Engineer.
Der heißt Gunnar Morling.
Gunnar Morling hat früher für Red Hat gearbeitet.
Zum Zeitpunkt dieser One Billion Road Challenge, von der wir gleich erzählen, hat er bei Decodable gearbeitet.
Das ist ein Apache Flink Software as a Service Startup, wenn man so möchte.
Apache Flink ist so Stream Processing.
Und inzwischen ist er auch Software Engineer bei Confluent.
Das ist die Firma hinter Kafka für die Leute, die es nicht kennen.
Und Gunnar hatte eine Idee.
Lass uns doch mal einen Performance Contest starten.
Da hat er sich gedacht, ich setze mich mal hin, tüftel mal ein bisschen und schreibe darauf einen Blogpost.
Den hat er dann am 1.
Januar 2024 veröffentlicht und die Challenge hat er gesagt, okay, die ich jetzt gleich beschreibe, worum es da geht.
So, wir starten jetzt am 1.
Januar 2024 und die geht jetzt ganz genau einen Monat bis zum 31.
Januar bis Mitternacht und dann schauen wir mal, was passiert.
Womit er nicht gerechnet hätte, der Performance Contest ging ein wenig viral und hat ihm ein wenig mehr Arbeit gemacht, als er eigentlich wollte.
Aber was hat er eigentlich in diesem Blogpost geschrieben?
Und zwar sagte er, lass mal Performance Contest machen.
Lass doch mal gucken, wie weit sich modernes Java performance-technisch eigentlich treiben lässt.
Und vielleicht kommen da Lösungen bei rum, von denen man auch noch etwas Neues lernen kann.
Das war die Motivation und da hat er sich eine Aufgabe ausgedacht.
Die Aufgabe, und das fand ich so schön, so hat er seinen Blogpost begonnen.
Deine Aufgabe, komm mal, solltest du sie annehmen?
ist trügerisch einfach.
Schreibe ein Java-Programm, das Temperaturmesswerte aus einer Textdatei ausliest und die Minimal-, Mittel- und Maximaltemperatur pro Wetterstation berechnet.
Es gibt nur einen Haken.
Die Datei hat eine Milliarde Zeilen.
Und ich weiß, du guckst ja nicht so viele Hollywood-Action-Filme, aber deine Aufgabe, solltest du sie annehmen.
Woher kommt das?
Äh, Matrix?
Mission Impossible.
Oder eigentlich deine Aufgabe.
Im Falle, dass du sie annimmst, klack, klack, klack.
Und danach zerstört sich dann dieses Memo selbst.
Dieser Blogpost hat sich nicht von selbst zerstört, aber ist trotzdem schon.
Also diese Textdatei hat auch eine relativ einfache Struktur.
Und zwar Hamburg, Semikolon, Temperaturwert, also 12,0.
Krakau, Semikolon, 12,6 und so weiter und so fort.
Das Programm, was du schreiben sollst, soll halt...
Den minimalen Mittelwert und den Maximalwert pro Messstation, also Hamburg, Krakau und so weiter, in alphabetischer Reihenfolge ausgeben.
Also Hamburg gleich 5 Grad ist der niedrigste Wert, 18 Grad ist der Mittelwert, 27,4 ist der Maximalwert, Komma, Krakau und so weiter.
In alphabetischer Reihenfolge.
Ziel der ganzen Challenge?
Wer kriegt es am schnellsten hin?
Und die Regeln, weil wir könnten jetzt hier Anarchie machen und keine Regeln haben, aber Gunnar sagte, Lass mal ein paar Regeln festlegen.
Und die fand ich auch ganz interessant.
Du darfst keine externen Abhängigkeiten nutzen.
Das bedeutet also eigentlich, wenn du so möchtest, nur Standard-Lib.
Java-only.
Also auch keine Derivate irgendwie wie Kotlin.
Und auch kein Java-Native-Interface.
Also dass du mit Java eine andere Sprache callst oder so.
Dann hast du ja schon über die JVM gesprochen.
Und bei Java gibt es ja auch alternative Runtimes.
OpenJDK, Gral-VM und so weiter und so fort.
Die wurden erlaubt.
aber nur, wenn sie in einem spezifischen Package-Manager verfügbar waren.
Also das nennt man SDK-Man.
Das ist ein Software-Development-Kit-Manager.
Du kannst dir vorstellen wie irgendwie so ein Paket-Manager für Java-Runtimes.
Dann fand ich sehr gut die Regel, kam ich auch initial gar nicht drauf.
Die Berechnung der Ergebnisse muss zur Laufzeit der Anwendung erfolgen und nicht zur Compile-Time.
Da gibt es ja ganz dreckige Elemente, die man da fahren kann.
Und nochmal ein paar mehr Informationen zu den Daten selbst.
Ich habe ja gerade Hamburg und Krakau als Wetterstation-Beispiel genannt.
Es gibt nur maximal 10.000 eindeutige Wetterstationsnamen.
Was ist erstmal deine Meinung zu der Aufgabe, zu dem Datenset und zu den Regeln?
Du als alter Universitätsprofessor und auch Dozent, das ist aber so eine klassische Aufgabe, die man auch den Studenten geben kann, um die gegeneinander antreten zu lassen, oder?
Oder hättest du noch eine andere Regel reingeführt?
Mein Initialgedanke war sofort, wie würde das in Node.js machen?
Mit JavaScript, Andi, für dich kann es schneller sein als Java.
Aber gut, das ist noch eine Nebenbaustelle.
Sonst ist es natürlich eigentlich ein sehr cooles Beispiel, weil es super einfach ist.
Also auch die Daten, es gibt ja nur zwei Werte, die Station und die Temperatur.
Weil ganz oft sind ja so Testdatensets irgendwie ganz groß, sondern da geht es halt wirklich darum, ganz einfaches Problem, viele Daten, wie kann ich das wirklich bis ins Kleinste optimieren?
Also du hast wenig Möglichkeiten, da jetzt mit den Daten irgendwas zu machen oder mehrere Baustellen, sondern es geht halt wirklich darum, wie kannst du das schnell einlesen, wie kannst du Strings verarbeiten.
Klassisch, so eine Datei hat halt ganz viele Strings, Text und wie kannst du das optimiert machen.
Also super klein.
Und ich habe mir jetzt gerade wieder überlegt, wie du das vorgelesen hast.
Wir sind jetzt schon wieder fünf Ideen gekommen.
Das könnte man eigentlich mit Studierenden so machen.
Da könnte man bei der Challenge mitmachen.
Die einen machen eine Datenbank, die anderen machen JavaScript und so weiter.
Eigentlich eine geniale Challenge.
Also ich bin voll überzeugt von der.
Ja, dann machen wir mal weiter.
Denn wenn du diese Challenge auch deinen Studenten gibst, so wie Gunnar es dem Internet gegeben hat, muss man natürlich auch eine Methode auswählen, wie man die ganze Sache denn testet.
Und Gunnar hat sich gedacht, okay, ich teste das alles.
Also nicht ich Andi, sondern ich Gunnar.
Und zwar...
hat er gesagt, alle Lösungen werden bitte als Pull-Request submitted bei GitHub.
Und irgendwann, nach dem 31.
Januar, also im Februar, schnappt sich Gunnar die Lösung und testet das zentral auf einem Hetzner-Server mit acht dedizierten CPUs und 32 GB RAM.
Er hat dann auch ganz genau geschrieben, wie er getestet hat.
Das bedeutet, er hat das Linux Time-Command zur Zeitmessung genommen.
Er hat jedes Submission fünfmal laufen gelassen.
Er hat das schnellste und das langsamste Ergebnis verworfen und den Durchschnitt aus den drei mittleren Übergebliebenen ermittelt.
Würde ich sagen, ist eine faire Runtime, oder?
Also eine faire Zeitmessung, um halt irgendwie so noisy neighbor und Co.
alles mal irgendwie auszuschließen.
Und natürlich das klassische Caching-Problem.
Weil wenn du die Daten natürlich vom Operating System schon im Cache liegen hast, im RAM liegen hast, dann hast du da natürlich auch einen Vorteil und das ist eigentlich ab dem zweiten Run hast du die Daten irgendwo im Cache liegen und erst ab dem zweiten Run hast du richtige Zeitmessungen und darum das Größte wegzuwerfen, den größten Messwert macht absolut Sinn.
Aber man merkt schon, du gehst schon in die richtige Ecke, weil was Caching und RAM und Co.
angeht, da kommen wir gleich auch nochmal ein bisschen zu der Kritik, weil es ist das Internet.
Du kannst ja nicht einfach einen Blogpost rauspacken und eine perfekte Lösung, die gibt es ja gar nicht.
Es findet sich immer irgendwer, der was zu mappern hat.
Aber wenn du das jetzt mal so hörst, was würdest du denn so schätzen, wo liegen wir denn da zeitlich bei den Ergebnissen?
Wir sind immer noch bei Java.
Was würdest du sagen, wie schnell kann man so ein Java-Programm?
laufen lassen, damit man die zu erwartenden ergibt.
Ja, es sind ja mal 13 GB an Daten.
Das heißt, man braucht ja eigentlich alleine schon 13 GB Transfer der Daten.
Also wenn du jetzt davon ausgehst, dass die CPU schneller ist als der Datentransfer, dann hast du zumindest die 13 GB Daten.
Und da sind wir da schon im Bereich.
Ich habe jetzt keine aktuellen Zahlen so im Kopf, was aktuelle moderne Hardware hinbekommt.
Aber ich würde mal sagen, 13 GB von Eine klassische Festplatte braucht sicher mehrere Sekunden.
Von einer SSD-Platte bist du wahrscheinlich irgendwo bei einer Sekunde, vermute ich mal, oder bei eineinhalb Sekunden.
Ist so eine grobe Schätzung von mir.
Aber auf jeden Fall, der Datentransfer an sich ist schon durchaus sportlich.
Ich muss zugeben, ich bin beeindruckt, Wolfgang.
Ich bin beeindruckt.
Also du bist wirklich auf dem richtigen Track.
Ich greife mal ganz kurz vor, weil nämlich auch diese Zahlen habe ich natürlich vorbereitet.
Eine moderne NVMe SSD schafft sequenziell circa 5 bis 7 GB.
Und eine klassische Server-SSD so circa 2 bis 3 GB pro Sekunde.
Also meinst du jetzt 2 bis 3 GB für eine Spinning-Disk-Serverplatte?
Nee, nee, für so eine sogenannte SATA-SSD.
Also der Unterschied zwischen NVMe-SSDs und SATA-SSDs ist in der Regel das Interface, über das sie kommunizieren, da NVMe-SSDs oft über das PCIe-Interface kommunizieren.
Das heißt, die Spinning-Disk ist wahrscheinlich gleich langsam, weil das Interface Bottleneck ist?
Ja, da bin ich jetzt zu weit von der wirklichen Hardware entfernt.
Ich habe gerade nochmal nachgeschaut.
Klassische Spinning Disks waren ja eher so bei 300 MB die Sekunde.
Habe ich es auch wieder so im Kopf und früher, die guten alten Zeiten.
Aber kommen wir zurück zu dem richtigen Track, auf dem du warst.
Also bei 13 GB Rohdaten ist allein der I.O.
auf realistischer Hardware circa 2 bis 6 Sekunden.
Und das bedeutet natürlich, dass allein bis das Programm deine Daten hat, 2 bis 6 Sekunden hat.
Warum dem aber nicht so ist, kommen wir gleich drauf.
Jetzt weiß ich gar nicht, hattest du mir jetzt schon eine Zeit genannt?
Nee, hattest du noch nicht, ne?
Du bist sofort auf das I.O.
gegangen.
Ja, ich habe mir gedacht, dass das eigentlich...
Der limitierende Faktor ist, weil meiner Meinung nach, hätte ich gesagt, die Daten kommen langsamer, als die CPU das verarbeiten kann, wäre jetzt so mein Tipp.
Das heißt, man kommt dann wahrscheinlich so irgendwo bei den eben zwei, drei Sekunden raus, wenn man das jetzt normal implementiert.
Okay, also Gunnar selbst hat, als er die Performance Challenge gelauncht hat, eine naive Implementierung gebaut.
Also wirklich hier Open File und geh durch die Zeilen und pack die alle in so ein Array und so weiter und so fort.
Und da hat es vier Minuten und 49 Sekunden gebraucht.
Ohne, wirklich so einfach völlig dumm runtergeschrieben.
Es lebe die Objektorientiertheit in Java.
Ja, also ich meine, make it work, make it beautiful, make it fast.
Nee, make it fast und dann beautiful.
Ist ja aber auch egal, er hat es make it work gemacht.
So, jetzt der Gewinner der ganzen Challenge hat anderthalb Sekunden gebraucht.
Und zwar lief die Gewinnerlösung auf der Gral-VM, dass...
Bedeutet, dass die Gewinnerlösung 188 mal schneller ist als Gunners native, nicht native, naive, die andere ist ja auch nativ, wir reden ja nur über Java, naive Lösung schneller ist.
Fun Fact, der hat mich enorm zum Grinsen gebracht, muss ich zugeben.
Der Gewinner ist Thomas Würtinger.
Thomas ist Vice President Software Development bei Oracle und Gründer der Graal-VM.
Also du hattest da jemanden, der wirklich, also wirklich weiß, was er tut.
Und das muss ich zugeben, er hat mich sehr zum Grinsen gebracht.
So, jetzt ist es aber so, das war jetzt die Gewinner-Submission von 1,5 Sekunden, wenn man Gunners Regelwerk an den Tag legt.
Gunnar hat aber während des Monats hier und da ein bisschen Kritik bekommen.
Deswegen hat er noch zwei andere Bonus-Ergebnislisten gemacht.
Und zwar habe ich gerade gesagt, er hat die ganze Sache auf einem Hetzner-Server abgefeuert mit acht dedizierten vCPUs.
Und dann wurde die Frage gestellt, ja Moment mal, aber was passiert denn eigentlich, wenn wir einfach mal mehr CPUs zur Verfügung haben?
Also hatten wir ja auch schon eine Episode dazu, dass ab und zu höhere Parallelisierung nicht unbedingt immer ein schnelleres Ergebnis ist.
Episode 180, habe ich natürlich gerade nachgeschaut, Skalierung um jeden Preis.
Da haben wir genau besprochen, ab wann rentiert es sich denn überhaupt, in die parallele Welt einzutauchen, weil da ja auch ein gewisser Overhead ist.
Hier in diesem Falle lohnt es sich.
Und zwar wurde derselbe Test mal mit 32 Cores durchgeführt, also die selbst Submissions.
Und dann ging es auf 300 Millisekunden runter.
Ebenfalls mit der Graal-VM war jetzt nicht die Lösung von Thomas, dem VP von Oracle.
Das bedeutet, dass dann die Lösung mit den 32 Cores fünfmal so schnell ist.
Das heißt, sie ist dann...
Fünfmal so schnell ungefähr, es sind aber nur viermal so viele Cores.
Genau.
Interessant.
Das wäre auch mal interessant herauszufinden, warum das sein kann, aber wahrscheinlich gibt es da auch nochmal irgendwelche Optimierungen, Caches, die da genutzt werden können.
Ja, normalerweise würde ich sagen, das übersteigt meine Gehaltsklasse, weil das tut es ja in diesem Fall auch, weil das weiß ich nicht warum.
Und dann gibt es noch eine Bonusliste.
In den Regeln stand ja, dass es maximal 10.000 einzigartige Wetterstationen gibt.
Jetzt ist es aber nur so, dass der Testdatensatz nur...
413 verschiedene Wetterstationen hat.
Leute, die sich ein bisschen mit Competitive Coding auseinandersetzen und Algorithmenoptimierung, wissen, da gibt es aber Potenzial.
Wenn du nämlich umso mehr Informationen über die Daten hast, die für den Performance-Test genutzt werden, desto eher kannst du natürlich auf diese Daten optimieren.
Und deswegen wurde mal ein neues Datenset erstellt, wo wirklich 10.000 verschiedene einzigartige Wetterstationen erzeugt wurden.
Und jetzt könnte man sagen, ja, nee, warum ist das denn relevant?
Ja, also auf der einen Seite, wenn du natürlich andere Strings hast, beeinflusst das natürlich die Entscheidungen, wie du das File parsst.
Wenn du kleinere Strings hast zum Beispiel, kannst du andere Hash-Funktionen nutzen.
Und wie gesagt, du kannst halt nicht deine Algorithmen auf die speziellen Datensätze anpassen.
Dataset-Overfitting nennt man sowas.
Dann kannst du nämlich alles relativ gut handtunen.
Ja, und das hat die Top-Ten-Liste dann auch nochmal ein bisschen durchgemixt.
Also kann man sagen, Nur weil die Lösung, die bei dem originalen Regelset gewonnen hat, das ist nicht automatisch der Gewinner dann, wenn die Testszenarien minimal geändert werden.
Ein Funfact gibt es jedoch.
Und zwar gab es einen Teilnehmer, der bei allen drei Tests, das bedeutet bei dem originalen Regelwerk, bei dem Regelwerk mit den 32 CPUs und mit den 10.000 einzigartigen Wetterstationen in den Top 3 war.
Eine einzige Submission.
Und da könnte man schon drüber sprechen, okay, das ist ja so die beste, würde ich sagen, well-rounded Lösung, oder?
Außer du hast nur ein Datenset, wo du 400 Stationen drin hast.
Also es kommt immer darauf an, was du für Daten hast am Ende.
Weil vielleicht hat die Lösung dann ein Problem mit 20.000 Stationen.
Also es ist immer die Frage, wie weit ist das Ganze optimiert.
Vielleicht auch noch jetzt nachträglich zur Erklärung, weil du jetzt gesagt hast, okay, die Strings sind dann länger geworden bei den Stationen.
Das würde ja auch heißen, die Transferzeit von Disk wird länger.
Und eigentlich wäre das ja...
Alles diskbound, wenn man das wirklich von disk laden würde.
Aber wie du ja initial schon gesagt hast, erstens wird das Ganze öfters ausgeführt.
Das heißt, diese ganzen Informationen von der Platte wandern mal zuerst in den RAM.
Entweder über ein Betriebssystem Cache und im RAM sprechen wir dann von 40 bis 60 Gigabyte pro Sekunde.
Das heißt, wir laden das gesamte File unter einer Sekunde eigentlich in Richtung CPU.
wenn das schon im RAM liegt.
Und dadurch kann man erst solche Mikrooptimierungen machen, weil sonst bin ich mir ziemlich sicher, dass eigentlich das Bottleneck die Festplatte wäre.
Ja, also jetzt greife ich mal ein bisschen vor, bevor wir zu der offiziellen Kritik an der Challenge kommen, denn du sprichst es ja schon an.
Also auch im Test hat Gunnar anscheinend die Datei bereits auf eine RAM-Disk gelegt.
Das bedeutet, auch im ersten Run lag die schon im RAM.
Die lag gar nicht, also die Testdatei lag gar nicht auf einer realen Festplatte.
Das bedeutet natürlich dann genau das, was du gesagt hast, dass der Disk-IO eigentlich ja maskiert ist, was in der realen Welt ja der dominante Bottleneck ist.
Also du misst ja eigentlich hier den reinen CPU- und Memory-Bandbreiten-Durchsatz und nicht den Bottleneck von Festplatten-IO, der eigentlich bei realen Big-Data-Workloads der Bottleneck ist.
Und somit hat das mit der realen Welt relativ wenig zu tun.
Aber die Challenge wäre sonst relativ langweilig, weil...
Wenn der limitierende Faktor die Festplatte wäre oder der Speicher, also SSD-Speicher, dann hättest du halt nichts zum Optimieren.
Dann könntest du auf der CPU-Ebene noch so viel optimieren.
Wenn das Limit die SSD ist, dann kannst du halt nichts machen.
Also es wäre eine langweilige Challenge.
Insofern ist es ja wohl sinnvoll, das so aufzusetzen.
Das ist nicht ganz richtig, denn einige Optimierungen werden jetzt besonders belohnt.
So handgeschriebene CPU-Instructions und so weiter.
Andere Optimierungen, wenn du die Datei von der Festplatte lesen würdest, könntest du trotzdem machen.
Wie zum Beispiel, du könntest den Page Cache umgehen, du könntest asynchrones Prefetching machen, du könntest vorher die ganze Sache komprimieren, du könntest vielleicht sogar eine spaltenorientierte Formate transferieren und ähnliches.
Also solche ganzen Tricks lässt du komplett außen vor.
Und ja, du hast recht, man kriegt das Ergebnis dann mit hoher Wahrscheinlichkeit nicht auf eine Sekunde.
Aber du kannst eine ganze Menge Optimierungen bezüglich I.O.
machen, Betriebssysteme und Co., die jetzt komplett außen vor gelassen werden.
Ist ein fair point.
Man muss dazu sagen, da musst du die Daten wieder umschreiben und das ist halt auch nicht realistisch, weil wenn du die Daten umschreibst, kann ich sie gleich zusammenfassen.
Aber wie dem auch sei, es ist die Challenge so definiert, dass es eben um Code-Optimierungen geht, die du beim Processing im CPU-Layer wirklich.
Und das finde ich auch schön an dieser Challenge, dass das eben so ein kleines Fenster ist, dass es genau um diese Dinge eben auch geht.
Jetzt ist natürlich die große Frage, wie kreativ ist das Internet geworden?
Oder zumindest die Leute, die eine Submission eingereicht haben, welche Optimierungen haben sie gemacht?
Und da waren ein paar Optimierungen dabei, die wollen wir jetzt mal so ein bisschen durchgehen.
Die sind vielleicht hier und da etwas Java-spezifisch, aber im Endeffekt lassen die sich, glaube ich, auf fast alle Sprachen übertragen.
Genau, man glaubt nämlich immer, dass das irgendwie dann irgendwelche JVM-Sachen sind oder sonstige Dinge.
Aber so grundlegende Optimierungen.
Kann man sowohl für Java machen, die würde man auch in C machen.
Die funktionieren bei BHB im Prinzip auch ähnlich, weil im Hintergrund halt trotzdem das Operating System arbeitet.
Also es sind schon allgemein gültige Optimierungen, die meisten zumindest.
Die erste Optimierung, die vielleicht gar nicht so offensichtlich ist, die für mich auch wieder interessant war, ist, dass natürlich eigentlich alle Lösungen parallel gearbeitet haben.
Also sie haben wirklich auf acht CPU-Kurs gearbeitet.
Okay, cool.
Da natürlich auch später das ganze Result-Set auch irgendwie ausgegeben werden muss, kann man sich denken, ja gut, dann habe ich dann vielleicht jetzt eine Hash-Map, da schreibe ich die ganzen Daten rein, Wetterstation irgendwie als Key und da drunter vielleicht nochmal eine Hash-Map oder einfach nur ein Array mit dem Min-, Mittel- und Max-Temperaturwert.
Und dann schreibe ich die einfach da rein.
Das Problem ist natürlich, wenn du auf acht Cores arbeitest, dann hast du natürlich Parallelisierung.
Dann, je nachdem, welche Hashmap du dann nutzt, kannst du vielleicht parallel schreiben oder die wird halt global gelockt.
In einem Performance-Kontext-Locking, na, ihr merkt schon, alles ein bisschen unvorteilhaft.
Eine der Optimierungen, die eigentlich jeder genutzt hat, lokale Hashmaps, damit jeder Thread halt wirklich eigenständig arbeiten kann, weil sonst würde viel zu viel Zeit zur Synchronisation und zum Locking halt verbraucht werden.
Und dann halt am Ende einfach gemerged.
Wobei auch da wieder, du musst natürlich die Merchzeit mitrechnen.
Also wenn du im ganz Allgemeinen das optimierst, kann natürlich auch sein, dass dieses Merchen am Ende wieder sehr teuer ist.
Aber üblicherweise würde ich jetzt auch mal sagen, das ist meistens der schnellere Schritt.
Vor allem, wir sprechen da ja von 400 Wetterstationen.
Das heißt, du musst nur 400 Wetterstationen merchen.
Wenn du jetzt 400.000 Wetterstationen hättest, sieht die Sache vielleicht schon wieder ganz anders aus.
Oder 4 Millionen Wetterstationen.
Das ist korrekt.
Aber ich glaube, das ist ja eine...
Sache, die man sehr schnell durchmessen kann.
Also hast du jetzt eine globale Hashmap oder hast du eine Hashmap pro Thread?
Das ist ja relativ schnell umgeschrieben.
Was auch noch ganz interessant war, war bei dieser Parallelisierung, wie die Daten aufgeteilt wurden.
Weil du hast ja eine große Datei, die du lesen musst.
Jetzt ist das Problem, du weißt ja nicht, wenn die Strings sind, wo ist denn ein sauberer Cut bei diesen Dateien.
Und was da gemacht wurde, ist, dass man grundsätzlich einfach mal die Datei aufsplittet.
Man zieht irgendwo die Grenze.
Und dann sagt man aber, jeder einzelne Thread fängt nicht am Start an, bei der Position, die mitgeteilt wurde, sondern dieser Thread schaut mal, wo ist denn die nächste neue Zeile.
Und erst die nächste neue Zeile wird dann genommen und von da aus wird gelesen.
Das heißt, wenn du in der Mitte von einer Zeile bist, dann ist das kein Problem.
Das heißt, du suchst dir die nächste Zeile und erst ab da fangst du arbeiten an.
Jetzt hast du natürlich das Problem, wenn du jetzt erst die zweite Zeile nimmst, wer bearbeitet die erste Zeile?
Und das ist dann einfach so gelöst worden, dass der Thread am Ende, wenn das Ende erreicht ist von seiner Position, der trotzdem nochmal weiterliest bis zur nächsten neuen Zeile.
Das heißt, diese verlorene Zeile wird vom Thread davor übernommen, dass also wirklich jede einzelne Zeile processed wird und keine Zeile verloren wird.
Und so kann man einfach mal stur diese Datei in der Mitte durchschneiden und das sauber verteilen.
Also es sind so Kleinigkeiten, die man einfach beachten muss.
In der echten Welt würde man es vielleicht weglassen oder einfach das sinnvoll durchzählen.
Aber wenn man halt diese Zeit nicht hat und nicht weiß, wo eine Zeile auffällt, wo eine Zeile beginnt, muss man dann mit solchen Taktiken arbeiten.
Aber wenn wir mal von dieser kleinen Optimierung unter Anführungszeichen oder sehr naiven Implementierung von Parallelisierung auf mehreren Threads auf eine Optimierung gehen, die wirklich extremen Impact hat und das ist eigentlich so die grundlegendste, wichtigste Optimierung meiner Meinung nach, ist natürlich dieses Problem, wenn du in Java einfach mal eine große Datei einliest.
Dann gehst du da Zeile für Zeile durch, erstellst Objekte.
Für jede Zeile gibt es ein Objekt.
Für jede Wetterstation, für jeden Namen gibt es einen neuen String.
Alles wird schön am Heap angelegt.
Du erstellst da Objekte, Objekte, Objekte.
Und danach kommt hin und wieder der Garbage-Collector vorbei und fängt dann an, eine Milliarde von Zeilen und Objekten zu Garbage-Collecten.
Und das wird dich einfach töten.
Also da wartest du auf den Garbage-Collector.
Keine Ahnung, wahrscheinlich 99% deiner Zeit ist nur mehr der Garbage Collector am Weg, irgendwie deine Objekte zu entfernen.
Und Objekte generieren natürlich kostet auch viel Zeit.
Das heißt, wenn man mit großen Datenmengen arbeitet, ist das eigentlich die schlechteste Idee, die man machen kann.
Und das ist ja nicht nur Java, was Objekte erzeugt, machen ja andere Sprachen genauso.
Und was sich da eigentlich so durchgesetzt hat, ist, dass man einfach mMap verwendet.
Das ist eine Möglichkeit vom Betriebssystem auf sehr große Dateien zuzugreifen.
Es wird in der Datenbankwelt verwendet.
Es wird überall verwendet, wo man eigentlich mit sehr großen Datenmengen handiert.
Das ist nichts anderes, als dass ich sagen kann, map mir doch eine große Datei in meinem Filesystem in einen virtuellen Speicherbereich.
Und dann greifst du nur mehr auf diesen virtuellen Speicherbereich zu und das Betriebssystem im Hintergrund managt alles für dich.
Das heißt, wann werden die Daten wirklich geladen von der Festplatte?
Wo werden die zwischengespeichert?
Wenn du was änderst, werden die wieder zurückgeschrieben.
Das heißt, dieses ganze Management im Hintergrund, wann werden welche Teile von der Festplatte geladen und geschrieben, das wird vom Betriebssystem übernommen.
Und du kannst eigentlich auf die Daten zugreifen, als hättest du einen riesengroßen Speicherbereich.
Der kann auch größer als der RAM an sich sein, weil das dann automatisch im Hintergrund gemappt wird.
Die Pages werden geladen, durch diese ganzen Caches durchgereicht und dann am Ende von deinem Programm überhaupt verarbeitet.
Und das ist natürlich super optimiert, weil das Betriebssystem das im Hintergrund macht.
Und wie gesagt, auch große Datenbanksysteme, wenn du da irgendein Datum änderst, eine Zeile änderst in der Datenbank, dann wird da im Hintergrund wieder dieses Mapped-File zurückgespeichert auf die Festplatte.
Und genau dieses Vorgehen haben die natürlich auch alle verwendet.
Das heißt, man mappt einmal diese 13 GB in einen virtuellen Speicher und geht dann schrittweise durch diese Datei durch und liest die Daten.
Und der große Unterschied ist, dass dann du, in Java keine Objekte hast, die du immer erzeugen musst, sondern du greifst direkt auf den Speicher zu.
Früher war das nur möglich mit Unsafe.
Unsafe ist in der Java-Welt eigentlich so ein Begriff, der auf einen gewissen Sprachumfangfunktionen hinweist, die eben unsafe sind, nicht sicher sind im Sinne von Speichermanagement.
Und wenn man zum Beispiel so einen Mapped-Byte-Buffer verwendet, der eben auf so einen M-Map vom Betriebssystem zugreift auf so ein virtuelles Speichersystem von einer Datei, dann wird da eben direkt auf den Puffer zugegriffen und nicht mehr auf irgendwelche Objekte im Heap und dann verliere ich natürlich auch gewisse Sicherheitschecks, die da im Hintergrund von Java natürlich, wenn es um normale Objekte geht, immer gemacht werden.
Und daher ist es natürlich für einen produktiven Betrieb eigentlich eher nicht zu empfehlen, beziehungsweise viele sagen dann halt auch, wenn ich schon unsafe verwende, warum verwende ich dann überhaupt Java?
Weil dann kann ich gleich C verwenden.
Da ist alles unsafe sozusagen.
Also bringt mir dann Java überhaupt noch einen gewissen Vorteil.
Seit Java 22 gibt es da auch dann Memory Segments, beziehungsweise seit 22 sind die eigentlich stabil und können auch verwendet werden, um etwas sichereren Zugriff zu haben auf Mapped Memory.
Das große Problem ist aber auch, dass da gewisse Checks im Hintergrund noch immer gemacht werden, die das Ganze langsamer machen.
Das Java-Team selber...
Sagt, da wird auch sehr viel gemacht.
2025 haben sie das mal in einem Talk auch wirklich angesprochen, was für Optimierungen sie machen und dass das Memory-Segment dann noch wesentlich schneller werden sollte und in Richtung Unsafe von der Performance gehen sollte.
Unsafe ist garantiert immer das Schnellste, weil da hast du einfach null Checks.
Und darum wurde das bei der Challenge eigentlich auch immer verwendet gegenüber, obwohl es Memory-Segment schon gegeben hätte, ist man dann am Ende trotzdem wieder auf die Unsafe.
Funktionen gegangen, weil man da einfach nochmal mehr Speed rausholen kann.
Jetzt ist es natürlich so, weil Unsafe mehr oder weniger deprecated wurde und das Java-Ekosystem sich natürlich auch weiterentwickelt wurde, haben wir mal nachgeschaut, in dem JDK 26, was aktuell veröffentlicht wurde, wird Unsafe defaultmäßig Exception schmeißen.
Das bedeutet, wenn du jetzt eigentlich dieselbe Performance-Challenge nochmal laufen lassen würdest mit Unsafe, wird dann eine Exception fliegen und wenn du die abfangen würdest, dann wird das natürlich auch negativen Impact auf deine zeitliche Performance geben, weil Exceptions schmeißen immer relativ teuer ist.
Vielleicht noch als Information, was das wirklich in Zahlen bedeutet hat damals, also von so einem normalen Approach auf einen Unsafe-Approach, da haben wir einen Speed-Up von ungefähr vier, das heißt auf ein Viertel der Zeit, die es davor gebraucht hat.
Also das sind schon riesen Größenordnungen, wenn man da in den Unsafe-Bereich hineingeht.
Wie der Name schon sagt, Unsafe ist leider auch in normalen Fällen unsafe.
Also wenn du nicht ganz genau weißt, was du da tust, solltest du das, glaube ich, nicht verwenden.
Für so eine Performance-Challenge sage ich, gib Kette, nutz es bitte.
Auf jeden Fall, wenn man mit großen Files arbeitet, M-Map, super Ding, funktioniert eigentlich in jeder Sprache und lagert viel ans Betriebssystem aus, was einfach meistens speedtechnisch eine gute Sache ist.
Aber auch da muss man sagen, es gibt trotzdem noch diese...
Einteilung von Pages, von Cache-Lines, das spielt alles dort natürlich genauso eine Rolle.
Das heißt, wenn man jetzt wahllos in dem virtuellen Memory herumspringt, ist es natürlich trotzdem langsam.
Weil im Hintergrund muss dann das Betriebssystem ständig irgendwie auf der Platte herumspringen und sich wieder andere Pages von der Platte holen.
Das heißt, es dauert dann immer lang.
Also man ist da jetzt nicht irgendwie automatisch superschnell immer.
Wenn man schlecht programmiert und irgendwie wahllos random herumspringt, wird es langsam bleiben.
Egal, ob es jetzt Memory Mapped ist oder nicht.
Aber ich komme jetzt einfach mal mit zwei Optimierungen, die auch von jedem genutzt wurden, die deutlich einfacher zu implementieren sind und deutlich einfacher auch zu verstehen.
Jetzt nicht immer diese Nmap und ich brauche ein Informatikstudio, um irgendwas zu finden.
Und zwar kümmere ich mich mal wieder um Hashmaps.
Ich habe gerade schon von lokalen Hashmaps gesprochen, die dann über Threads nicht geteilt werden müssen oder beziehungsweise nicht synchronisiert werden müssen wegen dem Locking.
Jetzt spreche ich mal über Custom Hashmaps.
Und zwar haben die ganzen Lösungen, auch immer die Standard-Hashmap ersetzt.
Die Standard-Hashmap, wenn du da Sachen reinpackst, irgendwann, weil die Standard-Hashmap weiß von vornherein nicht, wie viele Entries reinkommen, wenn du irgendwann dauerhaft da reinschreibst, kommst du an einen Punkt, da muss sie geresized werden.
Da müsste im Memory ein bisschen was umgeschaffelt werden.
Das wird alles transparent hinten dran gemacht.
Wenn du aber weißt, wie groß deine Thematik wird, wie viel Daten du da hast und so weiter, dann kannst du natürlich mit Fixed-Sized-Arrays oder mit Fixed-Sized-Hashmaps arbeiten, um das ganze Resizing natürlich zu sparen.
Und dann wird es natürlich interessant.
Dann kannst du natürlich die ganze Sache sogar so weit treiben, dass du Custom Hash Map implementierst und die Größe deiner Einträge ganz genau kennst.
In diesem Fall waren die Einträge dann alle 64 Byte groß, was dann ganz genau eine Cache-Line ist und somit könnte das ganze Working Set wirklich in den L1 Cache von der CPU passen.
Und das ist natürlich Wahnsinn.
Also dann brauchst du eigentlich gar nicht mehr irgendwie in Memory rumspringen, sondern hast das ganze Datensatz direkt in der CPU und das bringt dir natürlich einen schönen Boost, wenn du aber weißt, wie viele Daten du hast.
Wir haben jetzt nicht genau angeschaut, was die Implementierungen von den Custom Hashmaps waren, aber die einfachste Hashmap-Implementierung, die es ja an sich gibt, ist, du machst einen Modulu, du nimmst die String-Werte, den Integer-Wert von dem String in irgendeiner Form, konvertierst ihn zu einem Integer und machst einen Modulu.
und bekommst dann die Position von dem jeweiligen Eintrag.
Das ist ja so, was man so ganz basistechnisch mal lernt.
Und wenn man nur 400 Einträge hat, hast du keine Kollisionen, hast du keine Probleme, dann hast du da im Prinzip Modulo 400 und bist schon glücklich.
Ob jetzt Modulo die schnellste Variante ist, keine Ahnung, gibt es wahrscheinlich noch optimiertere Dinge, aber das wäre so die naive Herangehensweise.
Du machst immer einmal eine Modulo-Operation, die auch sehr schnell ist und hast schon die Position in der Hashman.
Eine andere, noch einfachere Optimierung, die sogar ich wirklich schon mal angewendet habe, ist Integer statt Float.
Ich habe gerade gesagt, die Temperaturwerte sind zum Beispiel 12,4, also 12,4 Grad Celsius.
Und ich glaube, man kann davon ausgehen, dass Temperaturen in der Regel von minus 99,9 Grad bis plus 99,9 Grad von Wetterstationen in der Welt sind.
wo eine Wetterstation steht, was über oder gleich minus 100 Grad ist oder plus 100 Grad.
Sollte bei Celsius selten vorkommen.
Wenn wir bei Fahrenheit sind, schaut das schon wieder anders aus.
Nee, aber wir kennen ja die Daten.
Wir sind in einem ordentlichen metrischen System und nicht in irgendwas amerikanischem unterwegs.
Jetzt gibt es einen simplen Trick, der wird auch oft bei finanziellen Werten angewendet.
Und zwar, dass man die ganze Sache einfach nicht als Float speichert, sondern als Integer.
Denn wenn wir...
davon ausgehen können, dass wir immer nur eine Nachkommastelle hat, dann multipliziert man den Temperaturwert einfach mit 10.
Dann werden einfach aus 12,4 Grad einfach 124.
Und Integer-Arithmetik-Operationen sind zwei- bis dreimal schneller auf klassischen CPUs als Float-Operationen.
Und wenn man dann somit mit Ganzzahlen arbeitet, brachte das in dieser Messung allein dieser Schritt plus 40% Speed, also minus 40%, also bis um 40% schneller, nur weil man die Temperaturberechnung von Float auf Integer umgerechnet hat, mit dem man einfach mal 10 rechnet.
Solche kleinen Tweaks finde ich unglaublich schön, weil sie sind super einfach zu verstehen, sie sind super schnell zu implementieren und sie sind einfach nicht kompliziert.
Was du jetzt halt einfach wegverschwiegen hast, ist, wie komme ich denn von dem Stringwert zu dem Integer?
Weil das ist ja die eigentliche Schwierigkeit.
Das Ganze liegt als String in der Datei.
String parsen ist immer super teuer.
Und auch da hat ein Teilnehmer, und zwar ein vietnamesischer Student, sein Handle ist Mary Kitty, hat da eine Optimierung geschafft, dass er wirklich auf Bit-Ebene diese ganze Umformung macht.
Das heißt, er sieht diesen String wieder als Bit-Reihenfolge an, macht dann eine sehr schlaue Bit-Operation, eine Multiplikation eigentlich auf CPU-Ebene und nimmt sich die Einzelteile so heraus von dieser Float-Zahl, dass er die Hunderterstelle, die Zehnerstelle und die Einerstelle, jeweils einzeln betrachtet, da eine Multiplikation drauf macht.
Lange Rede, kurzer Sinn, mit einer ganz simplen Operation von 18 Alu-Operationen, also das sind so die Grundoperationen auf der CPU, super wenig, schafft das, diesen kompletten String umzuwandeln in einen Integer, indem man so die einzelnen Stellen einzeln behandelt und diese einzelnen Behandlungen werden aber über eine Multiplikation parallel auf der CPU quasi durchgeführt.
Und so kann man das umformen, wenn man natürlich die Daten genau kennt.
Also es geht natürlich nur mit diesem Muster, mit diesem Format.
Und sonst, wenn man sich denkt, wie kompliziert es sonst ist, irgendwann ein String zu parsen, das sind natürlich Welten, was man sich da einsparen kann.
Ja, der vietnamesische Student, der hat sich die ganze Challenge angesehen und hat relativ schnell festgestellt, nach den ganzen ersten Optimierungen, die wir gerade besprochen haben, hier mit mMap und dem Integer und all sowas, war halt irgendwann das Parsen der Temperatur der Bottleneck.
Mit diesen Alu-Operationen, die hat er irgendwie, glaube ich, auf Twitter oder so veröffentlicht.
Da sind den ganzen Leuten irgendwie so die Kinnlatte runtergefallen, weil da niemand drauf kam.
Und also wenn der Wolfgang von Alu-Operationen spricht, der hat das gerade so weggemacht.
Das sind diese grundlegenden Rechen- und Logik-Operationen.
Also hier zum Beispiel Add, Substract, Increment, Dekrement, sowas.
Also all das, was so eine CPU halt super schnell kann.
Und das wurde eigentlich zum Standardteil für jede Lösung.
Und jetzt, da gibt es noch eine Seitenstory zu der ganzen Thematik.
Und zwar...
hat der vietnamesische Student die Lösung wohl veröffentlicht.
Dann hat der Vice President von Oracle, der da auch die Gewinnerlösungen gemacht hat, ihn offiziell ins Gewinnerteam aufgenommen.
Und laut Gerüchten, ich konnte es nicht verifizieren, ich habe wirklich hart gesucht und fünf AIs draufgeschmissen und so weiter und so weiter.
Alles etwas zeitlich, etwas unscharf.
Was aber auf jeden Fall der Fall jetzt ist, dieser vietnamesische Student arbeitet inzwischen für Oracle.
Also der Thomas, der VP bei Oracle und der Graal-VM-Projekt liebt, hat den Kollegen anscheinend durch diese Lösung ins Graal-VM geholt.
Und das muss man ja natürlich schon sagen, ist ja schon eine harte Story, sofern diese denn stimmt.
Ich konnte sie nicht 100% verifieren.
Ich bin jetzt schon ein bisschen enttäuscht, Andi, von deinen investigativen Journalismusfähigkeiten.
Du könntest ja dem Thomas einfach eine LinkedIn-Message schreiben.
War denn das so?
Hast du denn das wirklich gemacht?
Ja, was jetzt gerade läuft und jetzt gucken wir mal, ob das funktioniert oder nicht.
Der Thomas hat eine E-Mail von mir bekommen, ob wir nicht mal ein Interview zum Thema Graviel machen, weil ich habe nämlich festgestellt, dass der Thomas in der Schweiz lebt und in Österreich studiert hat.
Deswegen habe ich die Hoffnung, dass der Thomas auch Deutsch spricht und wir ihn mal zum Interview kriegen.
Und dann werde ich ihm, glaube ich, diese Frage mal stellen, sofern...
das denn auch was wird.
Ich möchte jetzt nichts versprechen, Talk is cheap, wir haben noch nicht geliefert, jetzt hast du mich aber dazu gedrängt, naja.
Aber wenn jetzt vielleicht gerade eine Kollegin vom Thomas zuhört, vielleicht könnt ihr ihn ja mal anstoßen.
Was aber da Mary Kitty auf der CPU-Ebene gemacht hat, ist natürlich ein ganz allgemeines Vorgehen und alle Lösungen arbeiten eigentlich sehr tief unten und man kann natürlich auf der CPU-Ebene sehr viel optimieren, wenn man nur die richtigen Instructions gibt.
Und ganz allgemein, vielleicht 7D hast du vielleicht auch schon mal gehört, Andi.
Single Instruction Multiple Data.
Das heißt, du gibst eine Instruction an und kannst dann aber auf einem breiteren Feld, auf mehrere Bytes, 32 Bytes zum Beispiel, dieselbe Operation durchführen, dieselbe binäre Operation, dieselbe Addition.
Das heißt, wenn man mehrere Daten schon in die Register reinlädt und dann die eine Instruction anwendet, dann kann die CPU das wesentlich performanter durchführen, parallel eigentlich durchführen.
Und man hat natürlich einen extremen Speedup.
Jetzt haben wir sehr viele ähnliche Daten.
Und wenn man natürlich schafft, mit SIMD Dinge durchzuführen, dann erhält man da dementsprechend einen Speedup.
Jetzt gibt es auch noch SWAR, nennt sich das.
Wird auch gerne so genannt, the poorest man SIMD.
Das heißt, ich mache eine parallele Operation auf einem Register, zum Beispiel auf einem 64-Bit-Register.
Ich sehe aber die einzelnen Werte in diesem Register.
als einzelne Werte an und mache dann wieder eine mathematische Operation.
Und genau das ist bei der Challenge genauso gemacht worden.
Das heißt, ich lade mal eine Zeile oder einen Teil einer Zeile in ein Register und prüfe, wo ist, jetzt muss ich aufpassen, dass du mich auch verstehst, Andi, wie heißt das, Semikolon, bei uns heißt es Strichpunkt.
Das heißt, in der CSV, ich muss ja wissen, wo ist der Trenner zwischen meiner Wetterstation und meiner Temperatur.
Und die Kosten für einen String-Vergleich, wo ist denn dieses Zeichen?
Die sind eigentlich sehr hoch.
Wenn ich das jetzt aber über SWAR mache, also SIMD within a register, heißt das ausgeschrieben, dann kann ich mit einem Bit-Muster überprüfen, wo ist denn dieses Semicolon in meinem String?
Gibt es da irgendwo ein Semicolon?
Weil das muss ich mal als erstes finden, damit ich überhaupt weiterarbeiten kann.
Und wenn ich das dann gefunden habe, dann gibt es auch nochmal so einen speziellen Instruktor-Befehl.
Für die CPU, das ist im Instruktorset mit dabei, nennt sich TZCNT.
Im Prinzip nichts anderes als zähle mir die Nullen nach einem Einser, nach dem binären Einser, da wo das Semikolon drin ist, dann bekomme ich die genaue Position.
Und das sind super wenig Alu-Operationen.
Das heißt, auch da kann ich wieder super viel Zeit rausholen und kann String-Operationen auf mathematische Alu-Grund-Operationen runterbrechen, um möglichst schnell meinen Strichpunkt, meinen Semikolon, in dem String zu finden und dann kann ich weiterarbeiten mit den restlichen Optimierungen, die wir auch schon erwähnt haben.
Aber auch da ist wieder natürlich wichtig, umso mehr ich sequenziell durch meine Daten durchgehen kann, umso mehr kann ich natürlich auch die Hardware-Optimierung ausnützen, weil am Ende kommt ja alles vom RAM oder von dieser RAM-Disk, geht durch meinen L3-Cache, L2-Cache, L1-Cache, bis es dann irgendwo im Register landet und umso mehr ich da sequenziell durchwandern kann, also 64-Bit für 64-Bit.
können meine Cache-Optimierungen dann auch auf der Hardware-Ebene gemacht werden und die bekommen über Pipelining-Effekt dann sehr sequenziell meine Daten rein.
CPU kann immer die gleichen Instructions eigentlich wieder ausführen.
Das heißt, ich bin überall optimiert und kann auf allen Systemebenen eigentlich meine Caching-Funktionen verwenden und hole dann auch wieder viel Speed raus.
Also so klassische Patterns sind eigentlich immer möglichst wenig.
ändern in meinen Instructions und möglichst gleiche Patterns anwenden, weil dann kann die Hardware auch super optimieren.
Du hast gerade schon Pipelining genannt und Vorausschaubarkeit von der CPU und da sind wir jetzt bei einem Stichwort Branchless Coding und das hat auch eine gewisse Relevanz bei dem ganzen Performance Contest gespielt, denn da geht es mal wieder um eine Performance Optimierung auf CPU-Ebene.
Jetzt mal ganz high level beschrieben, was Branchless Coding eigentlich ist.
Du schreibst deinen Code so um, dass if Verzweigungen eigentlich durch arithmetische oder bitweise Operationen ersetzt werden können, weil dann muss die CPU eigentlich nicht mehr raten, welchen Weg sie denn als nächstes nehmen soll.
Und so eine CPU, die hat eigentlich drei Ebenen.
Auf der einen Seite die Pipeline.
Hat der Wolfgang gerade schon besprochen.
Dann gibt es einen Predictor, einen Branch-Predictor.
Und dann gibt es sogenannte Branch-Misses.
Also der Kosten eines Fehlers, falls man einen falschen Weg gegangen ist, nennen wir es mal so.
Relativ simpel.
Leute, die Doktorarbeiten über Branchless Coding schreiben, würden mich jetzt, glaube ich, auseinandernehmen, weil ich die Terminologie nicht treffe.
Aber die meisten Leute von uns, würde ich mal sagen, sind nicht auf der Ebene unterwegs.
Im Endeffekt kann man die ganze Sache jetzt so optimieren, man kann seinen Code so umschreiben.
dass er relativ branchless ist.
Also, das bedeutet, der Branch-Predictor versucht zu raten, welcher der richtige Weg ist, den die CPU als nächstes nehmen muss.
Der versucht also die nächsten 15 plus 15, 20 Stufen für die CPU vorzuschreiben und dann in die Pipeline zu legen.
Typischerweise liegt so ein Branch-Predictor zu 95% richtig.
Wenn das der Fall ist, Ich sehe Performance fast gratis, weil die CPU einfach nur die Pipeline abarbeiten kann und einfach durchballern kann.
Ungefähr so wie ihr fahrt einen 911er Porsche und ihr habt eine freie Autobahn.
Wenn er jedoch falsch rät, dann habt ihr einen Branch Miss und dann muss die gesamte Pipeline geleert und neu befüllt werden.
Und jetzt könnt ihr euch schon vorstellen, je nach Architektur, je nach 15, 25 Zyklen kann das relativ teuer werden.
Und in dem Zeitspektrum, in dem wir jetzt hier gerade in dieser Performance Challenge unterwegs sind, ist dies natürlich teuer.
Deswegen gab es eine Optimierung von Thomas, dem Gewinner, der hat seinen Code so umgeschrieben, dass er möglichst wenig Branches hat und hat somit die Instruktionen in der Pipeline zwar erhöht, also die CPU hatte mehr Arbeit, er hat aber die Branch Misses selbst von 6% auf 0,3% reduziert und ist somit...
deutlich schneller geworden und es hat somit auch den Gewinn gekriegt.
Also Branchless Coding war seine letzte Optimierung.
Der hat zehn Versionen geschrieben.
Alle zehn Versionen sind in seinem GitHub-Repository verfügbar.
Die haben wir auch unten in den Shownotes verlinkt.
Ihr könnt euch das mal ansehen, wie das funktioniert.
Ganz interessante Thematik, auf welcher Ebene man da alles optimieren kann.
Ob man das jetzt für Produktionscode machen muss oder nicht, weiß ich jetzt nicht.
Also für meine Webapplikation...
Wahrscheinlich nicht notwendig, weil da HTTP eh alles sprengt, also von daher.
Also ich glaube, dass es grundsätzlich, wenn du Code schreibst und der Code extrem oft ausgeführt wird, dass das durchaus einen Sinn macht und dann solltest du eben in deinem If-Statement vielleicht nicht irgendwas drin haben, was einmal so ist, einmal so, also so ein Check auf gerade und ungerade und dann hast du ganz viel darunter mit dem Check gerade, ungerade.
Wenn du den irgendwie wegoptimieren könntest, dass das halt eher so ein Fall ist, wenn ein Fehlerfall da ist, dann geh in den Else-Zweig.
Und sonst ist der klassische If-Zweig immer der richtige, weil dann hast du wieder so ein Pattern, was immer angewandt wird, was immer gleich ist und alles, was gleich ist, ist für die CPU und für das ganze System einfach immer von Vorteil.
Und darum ist es auch wichtig, dass du, wenn du durch ein Array durchläufst, halt so durchläufst, wie das im Memory liegt und nicht irgendwie random springst, weil sonst springst du alles durcheinander und dann fallen überall alle Caches raus.
Du hast wieder womöglich irgendwo andere Branches, Branch Misses und das macht dann einfach alles teuer.
Und wenn du auf so einer Ebene bist, dass du da optimieren musst, dann macht sowas natürlich definitiv Sinn, das mal mitzudenken und vielleicht auch an Ifs wegzulassen.
Das ist ja auch möglich.
Vielleicht kannst du irgendwas mit einem Bit-Shift zum Beispiel machen, anstatt mit einem If.
Jetzt haben wir schon über ein paar Optimierungen gesprochen.
Relativ einfache, wie zum Beispiel lokale Hashmaps, Custom Hashmaps, Integer statt Float und so weiter.
Ein paar kompliziertere, Branchless Coding, SIMD mit dem Register.
allem Pipapo, irgendwelche Bit-Instruktionen, Alu-Operationen.
Also da kann einem schon mal der Kopf rauchen, aber auch für die Infrastruktur-Leute ist was dabei.
Und zwar kann man eine ganz einfache Optimierung machen und zwar 8 von 10 Lösungen in den Top 10 haben die es auch gemacht.
Die haben einfach die Standard-JVM ausgetauscht und zwar haben die einfach mal die Graal-VM genutzt und wer jetzt nicht aus dem Java-Umfeld kommt, stellt sich dafür die Frage, was ist denn jetzt schon wieder die Graal-VM?
Gar kein Problem, wir haben euch gecovert.
Und zwar, die Graal-VM ist auch von Oracle entwickelt, ist eine alternative Laufzeitumgebung für Java, beziehungsweise in JDK, ist voll kompatibel zum Java-Standard, hat aber ein paar andere Elemente, die besonders für diesen Performance-Contest sehr relevant sind.
Auf der einen Seite macht die Graal-VM sogenanntes Add-of-Time-Kompilierung.
Das bedeutet, die Graal-VM erzeugt eine ausführbare Datei und braucht bei der Ausführung keine JVM zur Laufzeit mehr.
Also es wird nicht so wie die...
JVM auf der virtuellen Maschine ausgeführt, sondern die Graal-VM kompiliert ahead of time den ganzen Code in einer ausführbaren Datei.
Dann im Gegensatz oder im direkten Vergleich zur JVM hat die Graal-VM einen extrem schnellen Start.
Das ist natürlich für einen Performance-Contest, da wo es irgendwie um teilweise Millisekunden geht, schon hart relevant.
Die zeichnet sich wohl auch durch geringen Speicherverbrauch aus.
Zumindest in der Basisversion.
Und was auch ganz interessant ist, die kann auch mit mehreren Sprachen umgehen.
Das bedeutet, die Graal-VM, wenn man da mal ein, zwei Extensions reinpackt, Truffle-Framework heißt das jetzt in diesem Fall, dann kann man da auch JavaScript heißen und Ruby und C und C++ in derselben Runtime ausführen.
Also Interoperabilität zwischen Sprachen.
Ganz interessant.
Das ist so mal das globale Bild, was eigentlich die Graal-VM ist.
Ich habe gerade gesagt, 8 von 10.
Lösungen in der Top 10 nutzen, die Gral-VM.
Ich möchte aber auch positiv hervorheben, dass die schnellste JVM-Implementierung auf Platz 4 liegt.
Also das geht alles auch mit der JVM.
Eine andere schöne Optimierung oder ja, also ich weiß nicht, ob man es Optimierung nennen kann.
Ich würde es eher ein Hack nennen, aber auch das sei ja bei einer Challenge durchaus erlaubt, ist, dass dieses Memory Mapping, was wir zuerst erwähnt haben, dass du das in den Memory Maps.
Das musst du ja dann auch irgendwann aufräumen.
Das heißt, wenn du das wieder schließt, dann muss das wieder alles gecheckt werden.
Das braucht auch wieder Zeit.
Das muss alles ordnungsgemäß abgeschlossen werden.
Und das braucht halt bei 13 GB 100 Millisekunden.
Jetzt ist ja das eigentlich unnötig und will man ja eigentlich gar nicht machen.
Was ich jetzt ein paar Schlaue ausgedacht haben ist, wir können das Ganze ja in den Subprozess auslagern.
Und wenn wir fertig sind mit unserer Berechnung, dann lassen wir einfach den Parent, also den Parent-Prozess, den eigentlichen Berechnungsprozess, weil wir haben ja das Ergebnis schon ausgegeben, dann terminieren wir das Ganze und dann läuft eigentlich dieser Kind-Prozess, den wir da weggespawnt haben, der läuft im Hintergrund weiter und wird dann vom Betriebssystem als Zombie-Prozess irgendwie verarbeitet und dann gekillt, beziehungsweise es wird das halt alles geschlossen und verarbeitet und im Hintergrund passiert das alles.
Jetzt, indem man aber im Parent killt, frühzeitig, ist man eigentlich noch gar nicht fertig mit dieser ganzen Arbeit.
Aber nachdem die im Zombie-Prozess dann vom Betriebssystem im Hintergrund erledigt wird, ist man offiziell schon beendet und hat das Ergebnis schon ausgegeben.
Und die offiziellen Regeln erlauben das auch, weil die Zeit wird nur gestoppt, bis dieses Endergebnis ausgegeben ist.
Wenn da im Hintergrund noch irgendwas passiert auf Betriebssystem-Ebene, dann ist das völlig okay.
Und da holst du wieder 100 Millisekunden raus, was ja bei einer 1,5 Sekunden Laufzeit schon einiges an Zeit sind.
Also es sind 7%.
In dem Fall.
Und wenn du sieben Prozent rausholen kannst, ist das natürlich super.
Also das ist eher ein Hack, weil die eigentliche Berechnung wird jetzt nicht schneller dadurch, aber zumindest die Zeitnehmung wird positiv beeinflusst, nennen wir es mal so.
Habe ja einen sehr netten Hack gefunden.
Win ist Win.
Also ich würde sagen, kennst du die Regeln, kennst du die Daten, kannst du optimieren.
Und das finde ich immer sehr, sehr lustig.
Und deswegen haben wir auch eine ganze Menge dieser Optimierung mal durchgesprochen, weil da ist schon sehr, sehr viel Kreativität bei.
Jetzt ist die ganze Challenge.
aber auf Java ausgelegt worden.
Und das Internet hat sich natürlich entnehmen lassen, einfach mal andere Sprachen auch zu testen.
Es gab nie eine offizielle Challenge zu Sprachen wie C, Go, PHP oder ähnliches.
Aber es gibt im GitHub-Repository der Challenge in den Diskussionen ein sogenanntes Show-and-Tell.
Und da könnt ihr euch etliche Lösungen zu anderen Sprachen auch ansehen.
Zum Beispiel die schnellste...
Version, die recorded wurde, wurde in C geschrieben, die knapp über einer halben Sekunde liegt bei 8 Cores.
Also auch nochmal dreimal so schnell wie die schnellste Java-Lösung.
Und was es eigentlich ist, es ist eigentlich C mit einer ganzen Menge...
CPU-Instruktionen, sogar mit so einem Befehls-Erweiterungssatz, der nur auf Intel-CPUs läuft, weil da wurde dann direkt drauf optimiert, okay, wir wissen ja auch, auf welcher CPU die Tests durchgeführt wurden, deswegen nehmen wir direkt so einen Erweiterungssatz für die Befehle zu.
Also auch schon ziemlich krude, aber mal recht interessant.
Dann habe ich mir mal als alter Go-Fanboy auch die Go-Lösung mal angesehen und wenn man sich die mal so ansieht, dann hat die eigentlich eine ganze Menge derselben Optimierung drin.
Erst relativ naiv implementiert, danach wurden Pointer verwendet, danach wurde kein Float-Parsing mehr genutzt, sondern es wurde ein Custom-Temperatur-Parser implementiert.
Danach hat man eine eigene Hashtable gebaut, anstatt die aus der Standard-Lib zu nehmen und danach hat man Parallelisierung draufgesetzt.
Alles ähnliche Optimierungen wie bei Java auch.
Die schnellste native PHP-Lösung fand ich auch ganz lustig.
Die liegt bei 15 Sekunden, also schon noch ein bisschen was.
länger.
Aber es gab auch eine kreative Lösung, wie man PHP noch schneller machen kann.
Die wollte ich jetzt hier auch nicht unerwähnt lassen.
Und zwar hat jemand, das wird glaube ich nicht mehr ins Regelwerk passen, aber einfach nur der Kreativität halb.
Die original PHP-Standard-Lip-Lösung wäre 15 Sekunden.
Dann hat noch jemand das FFI-Interface von PHP genutzt.
Das ist eigentlich so wie JNI, dass du Foreign Functions callen kannst.
Und zwar hat irgendjemand eine PHP-Lösung geschrieben, die dann unten drunter eine andere Rust-Lösung einfach callt.
Kann man machen.
Dadurch kriegt man PHP auf 1,9 Sekunden runter.
Also eigentlich wird alles in Rust berechnet, nur die Ausgabe wird über PHP gemacht.
Jetzt können wir darüber streiten und philosophieren, ob das dann eine PHP-Lösung ist oder eine Rust-Lösung.
So, Wolfgang, deine Meinung, ist das PHP oder ist das eine Rust-Lösung?
Ja, eigentlich hätte ich jetzt gesagt, ist schon eine Rust-Lösung, aber dann habe ich mir überlegt, eigentlich, ich meine, diese ganzen...
Sprachen, Python, BHP, die basieren alle auf irgendwelche Lips, die darunter liegen.
Also auch Java basiert natürlich auf Lips, die darunter liegen.
mMap ist auch vom Betriebssystem.
Also es verschwimmt da eigentlich eh schon ziemlich.
Also nur zu sagen, wenn man eine interne Lip verwendet oder eine Lip, die darunter sitzt, schwierig zu sagen.
Aber am Ende ist dann die Frage, was macht die Lip immer?
Und was verwendest du ganz unten?
Was du darüber stülpst, ist ja dann eigentlich eh egal.
Und jetzt kommt noch ein bisschen was fürs Grinsen.
Es hat sich jemand die Arbeit gemacht und hat die One Billion Road Challenge in AWK gelöst.
Die AWK-Lösung hat ganze elf Zeilen, was auch sehr beachtlich ist und braucht circa sechs Minuten.
Als ich mir den Code angesehen habe, habe ich gedacht, ich kannte AWK.
Nein, ich kannte nicht AWK.
Verlinkt man natürlich auch in den Shownotes.
Und dann für dich, Wolfgang, speziell habe ich auch noch zwei One Billion Road Challenges in SQL rausgesucht.
Und zwar einmal mit Clickhouse, dann auch mit DuckDB.
Nicht überraschend, muss man sagen, hat der Datenbankimport schon 27 bis 30 Sekunden gedauert und dann die Query ging so auf 6 Sekunden, um die richtigen Daten zu machen, weil die ganze Berechnung halt in SQL gemacht wurde.
Aber eine Billion oder eine Milliarde Datenbankzeilen nach DACDB oder nach Clickhouse laden dauert halt schon, obwohl, ich muss sagen, Die Clickhouse-Lösung hat sogar noch nichtmals die Daten in die Clickhouse-Datenbank geladen, weil das hätte länger gedauert.
Die Clickhouse-Lösung hat einfach nur eine Query gemacht, die dann direkt auf das CSV über das Filesystem zugreift.
Und das ist natürlich auch eine interessante Geschichte.
Das bedeutet, da wurde nur wirklich die Query-Engine genutzt und die Daten wurden gar nicht in die Storage-Engine vom Clickhouse geladen.
Mir kommt es ja nur schweren Herzens jetzt über meine Lippen.
Aber ich habe eine Implementierung in Oracle gefunden.
Die habe ich auch gefunden, die habe ich rausgelassen.
Und die braucht nur 8 Sekunden.
Ja, ich meine aber gelesen zu haben, dass die den Import rausgerechnet haben.
Ja, die lesen direkt den CSV ein.
Das ist eine externe Tabelle.
Keine Ahnung, ob die jetzt im Vorhinein schon irgendwie in Memory und Umständen gemappt wird.
Das weiß ich jetzt nicht, aber...
Ja, scheinbar ist Oracle in manchen Bereichen zumindest recht schnell, weil es gibt, glaube ich, auch noch eine Postgres-Variante, die irgendwie acht Minuten braucht oder so, aber die Dinger sind halt einfach nicht fürs Bowsing gedacht und die machen halt dann wirklich ganz klassisches String-Bowsing und da merkt man halt dann, dass das einfach sehr, sehr teuer ist, wenn du halt diese Zeichen für Zeichen wirklich String-mäßig beachtest und nicht dieses Format, was eigentlich dahinter steckt, dann hast du einfach...
einen schweren Stand mit solchen Techniken.
Und jetzt höre ich manche Leute, die den Podcast-Gast schon hören, Jungs, schmeiß doch mal eine GPU drauf.
Das war alles viel schneller.
Ja, hat der Jakob gemacht.
Der Jakob ist sogar Senior Software Engineer bei NVIDIA.
Der hat sich die One Billion Road Challenge mal angenommen.
Und er hat mit Dusk, das ist eine Python-Bibliothek für Paralleles und Verteiltes Rechnen, und mit QDF, was eine Python-GPU...
DataFrame Bibliothek ist, wo er beides auch maintainer von ist, hat die ganze Sache mal auf echt heftigen Grafikkarten, auf zwei RTX 8000 ausgeführt und siehe da, es hat viereinhalb Sekunden gedauert.
Also da sieht man auch, dass mit Peak-Hardware und etwas Abstraktions-Overhead doch langsamer wird als klassisches Java auf acht CPUs.
Aber da wurde auch gesagt, okay, die ganze Challenge ist einfach nicht für GPUs, für sogenannte FPGAs, Field Programmable Gate Arrays geeignet, weil die Berechnung halt sehr trivial ist und die Read Patterns halt sehr sequenziell sind.
Und du hast dann auch noch dieses Lookup Hashmaps, die du irgendwie umformeln musst.
Das geht ja auch nicht.
Alles, was random ist, ist mit GPUs natürlich sehr böse.
Zusätzlich glaube ich jetzt, dass es auch ein Problem ist, wie du mit Strings umgehst.
Und wenn du das davor umrechnest, wieder in Integer-Werte, hast du wieder dasselbe Problem.
Also ist die Frage, könntest du solche Lösungen, wie es in den Challenge-Lösungen vorgekommen sind, wo du dann das Ganze binär betrachtest oder so, vielleicht könntest du es dann auf GPUs umformen, vielleicht würde es dann auch schneller werden.
Aber es ist halt einfach am Ende gar nicht so ein großes Datenset für eine GPU, muss man auch sagen.
Wenn man das jetzt als Integer sieht, sind es schon keine 13 Gigabyte mehr.
Und viele Strings, da ist GPU auch nicht gut, keine mathematischen Berechnungen.
Genau, es ist halt keine klassische Matrix- oder Vektorberechnung.
Es ist halt Min, Max und den Mittelwert.
Also von daher, aber auch mal interessant.
Aber hätte ich mir auch gedacht, dass es schneller ist als vier Sekunden, ehrlich gesagt.
Ja, wahrscheinlich, das kriegst du vielleicht wahrscheinlich auch noch ein bisschen runter.
Da ist ja auch Python noch dabei und Pipapo.
Aber ich glaube jetzt auch nicht, dass der Senior Software Engineer von Nvidia so viel Zeit reingesteckt hat.
Ja, wobei er kennt das dafür sehr gut und kennt...
Alle Basistricks zumindest.
Also wenn er selber Maintainer ist, würde ich mir schon vorstellen, dass er einen ganz guten Ansatz wählen könnte dementsprechend.
Aber ist schon interessant.
Für manche Challenges ist halt GPU auch nicht das Wahre.
Naja, aber an so einer Performance-Challenge, besonders wenn es um Performance geht, da fühlen sich manche Leute ja immer geknickt.
Kritikpunkte hatten wir schon während der Episode ein bisschen angesprochen.
Zwar das Dataset-Overfitting.
Das bedeutet, dass du dein Algorithmen ganz genau auf das Datenset, auf dem getestet wird, optimierst.
Dann hatten wir so ein bisschen über das Cache-Problem gesprochen, dass die Daten auf einer RAM-Disk liegen und somit manche Optimierungen, die auf das Lesen von der Festplatte sind, halt nicht relevant sind.
Es gab aber noch eine dritte Kritik und zwar zur Architektur der CPU.
Und zwar wurde...
eine Zen 2 CPU genutzt.
Und die Zen 2 CPU ist von der Zenbleed-Lücke betroffen.
Das ist eine CPU-Lücke aus 2023, die es möglich macht, sendible Daten abzufangen.
Und diese Lücke wurde auf der CPU mit einem Micro-Code-Update gepatcht.
Und dieser Micro-Code-Update verändert die Performance messbar.
Und die Teilnehmer, die das dann auf einer gepatchten CPU ausgeführt haben, haben 5-10% Abweichungen von den jeweiligen Lösungen gefunden.
Und wie relevant das jetzt ist, weiß ich nicht, weil da ging es jetzt nicht um viel Geld.
Ich glaube, Gunnar hat am Ende ein T-Shirt und zwei Tassen rausgehauen oder so.
Also da wurde jetzt keine wirklichen Preise vergeben.
Von daher würde ich sagen, liebes Internet, finde ich mal wieder spannend, wie tief eigentlich auch solche Diskussionen und Kritikpunkte gehen können.
Wenn du jetzt zurückblickst.
Auf diese ganzen Optimierungen und was du da jetzt alles gelesen hast, was bringt dir das im Alltag?
Nummer eins, ich habe hoffentlich mal den Mythos zerstört, dass Java langsam ist, obwohl ich kein JVM und Java Fan bin.
Ich oute mich.
Aber nach solchen Zahlen kann man wohl sagen, die JVM ist kein Performance Verlierer mehr.
Ja, sie ist zwei bis drei Faktoren langsamer als handgeschriebenes C.
Okay, ist auch schwer zu übertreffen, aber...
Der generelle Mythos, dass Java langsam ist, wurde, glaube ich, erst mal widerlegt.
Und ich glaube, auch die letzten haben es hoffentlich jetzt verstanden.
Ich sage aber auch, dass der ganze Code, den wir da besprochen haben, vielleicht kein realer Produktionscode ist.
Denn ein Großteil der Leistungssteigerung ist halt darauf zurückzuführen, dass halt irgendwie alle Best Practices über Bord geworfen wurden.
Also irgendwelche Validierungen, Bound Checks, Hashtable Resizing und so weiter und so fort.
Würde ich so nicht shippen.
Und ein Teilnehmer hat auch in einem GitHub-Issue geschrieben, der Code der schnellsten Beiträge unterscheidet sich stark von dem, was meine Kollegen und ich in unserem täglichen Arbeit schreiben.
Und da würde ich sagen, das stimmt, das stimmt.
Also es hat eigentlich hier nichts mit Produktionscode zu tun, sondern ist eher eigentlich so eine schöne Spielwiese.
Aber, und jetzt kommen wir zum positiven Punkt dieser ganzen Thematik.
Gute Performance und gute Lesbarkeit ist gleichzeitig machbar.
Denn viele Sachen, die wir besprochen haben, sind eigentlich Basisfehler und einfach nur gute Arbeit.
Diese Bit-Operation mit diesem Temperatur-Parser von diesen vietnamesischen Studenten, Wahnsinn.
Das ist einfach nur gute Arbeit.
Und die ganzen Performance-Gewinde, da steckt keine Magie drin, sondern teilweise, würde ich sagen, auch die Vermeidung von Anfängerfehlern.
Gut, die Bit-Instruktion mit den Alu-Operationen von den Witt-Ammelsen-Studenten würde ich jetzt nicht als Anfängerfehler bezeichnen, aber dass man jetzt zum Beispiel die Float mal 10 multipliziert, damit man einen ganzen Integer hat, das ist für jeden einfach erlernbar, würde ich mal sagen, oder?
Und du sparst ja auch die ganzen Rundungsfehler, die du hast.
Also was du bei den Currencies, bei den Währungen ja üblicherweise genauso machst, dass du Integer-Werte hast, das sind dann wirklich Fehler, die man macht.
Zu deinem Argument, schön lesbar, ja, das ist die Frage und Da bin ich auch eher auf der Seite, wenn mein Code 20 Sekunden dauert, anstatt drei Sekunden, wenn der beim Import einmal am Tag ausgeführt wird, dann bevorzuge ich die Lesbarkeit anstatt irgendwas Codiertes auf Bit-Operationsebene, was dann nur 50 Prozent von meinem Team verstehen.
Also ja, ich glaube, da ist Lesbarkeit dann schon auch wichtiger als die eigentliche Performance, aber es kommt immer darauf an, wo man, in was für einem Bereich man wirklich arbeitet.
Und vielleicht können wir da ja mal eine Episode machen mit jemand, der wirklich performanceorientiert ganz unten arbeitet.
Gut, der würde wahrscheinlich sagen, unser Code ist auch schön.
Das müssten wir dann wahrscheinlich beurteilen.
Aber würde mich mal interessieren, so Leute, die wirklich, keine Ahnung, an Datenbank-Index-Strukturen, irgendwas, Memory-optimierte Index-Strukturen wirklich arbeiten.
Schaut dieser Code noch schön aus oder ist er einfach auch unlesbar und du musst da irgendwie zwei Jahre dort arbeiten, damit du nur annähernd irgendwas verstehst?
Ja, ich kann jetzt auch sagen, Es gab auch eine Submission und zwar von einer Person, Alexei, Nachname kann ich nicht aussprechen, er gilt in der OpenJDK-Welt wohl als Performance-Guru.
Er hat eine Submission hingelegt, die allein 190 Zeilen Kommentare hat, wo dann erklärt wird, warum er alles macht.
Also das ist vielleicht auch ein Gegenbeispiel zur guten Lesbarkeit.
Aber es ist mal interessant, das zu lesen, weil da kannst du eine ganze Menge lernen.
Was aber auch ein Learning ist, ist das eine gute Community-Challenge.
eine schön organisierte und gut designte Community Challenge, eine klassische Konferenz in puncto Bildung auf jeden Fall schlagen kann.
Denn so viel Wissen, wie in diesem GitHub-Repository von dieser One Billion Road Challenge steckt, ich glaube, das kriegst du nicht auf einer Konferenzreihe mit.
Also da, wer wirklich mal so einen Nerd-Spielplatz haben möchte, der geht mal da rein, liest sich ein bisschen Quellcode durch, ein bisschen die Diskussion.
Das ist schon spannend.
Es gab ein paar Leute, die haben versucht, die ganze Sache auf andere Sprachen zu übertragen.
Es gab auch eine GitHub-Organisation, One Billion Road Challenge, aber die wurde inzwischen archiviert und das ist nie wirklich viral gegangen.
Also sie haben es versucht dann auch in JavaScript zu machen, so wie du initial das auch in TypeScript machen wolltest oder auf Node.js hast du gesagt.
Und ist aber nie wirklich viral gegangen.
Und jemand hat einen Nachfolger gemacht, eine sogenannte One Trillion Road Challenge.
Das ist ein 2,5 großes Terabit-Paket-File auf S3.
Ist auch nie wirklich viral gegangen.
Also diese ganzen Spin-Offs, nette Idee, aber ich glaube, der Hype ist jetzt vorbei.
Finde ich aber eigentlich schade.
Wäre eigentlich schon cool zu sehen.
Es gibt ja auch andere Challenges, auch so im universitären Umfeld oder so im hybriden Umfeld, wo es bei einer Konferenz jedes Jahr irgendwelche Challenges gibt.
Ich kenne es nur aus dem Recommender-Bereich.
Bei der Rexis gibt es immer eine Challenge.
Haben wir damals.
mit Trivago auch mitgemacht und ein Datenset zur Verfügung gestellt.
Vielleicht sind dann die Probleme komplexer, dass es interessant bleibt, dass es einfach ein zu einfaches Problem ist.
Aber grundsätzlich solche Challenges finde ich schon super spannend und vielleicht auch im kleinen Bereich, wie ich erwähnt habe, so mit Studierenden oder mal selber was ausprobieren.
Mir juckt es eigentlich auch schon unter den Fingernägeln, irgendwie das auszuprobieren, ob ich mit SQL schneller sein kann als die anderen, die das mit SQL gemacht haben.
Oder JavaScript würde mich auch einmal reizen, einfach um zu sehen, wie schnell das eigentlich...
funktioniert oder nicht funktioniert eben.
Ja, eine Kritik muss man schon sagen.
Also diese ganzen Zeiten, die wir hier während der Episode genannt haben für Go, für C, für SQL und so weiter, die sind natürlich jetzt nicht vergleichbar mit den originalen Werten, weil das natürlich nicht die gleiche Testausführung war.
Also da hat jetzt irgendjemand diese One Billion Road Challenge genommen, hat die in SQL gemacht, hat die auf irgendeine Cloud Note gepackt und hat dann diese Zeiten gepostet.
Also das sind jetzt nicht diese Zeiten, die von Gunnar, dem Organisator, selbst ausgeführt wurden.
Der hat das nur für Java gemacht und nur in diesem Zeitraum und alle anderen Zeiten danach sind halt, ich sag mal, selbst reported und selbst ausgeführt.
Da hast du natürlich eine große Varianz von eigentlichen Hardwarechips und allem drum und dran, was natürlich die ganze Sache komplizierter macht.
Wenn du jetzt natürlich jetzt diese Challenge machen würdest, replizieren würdest bei deinen Studenten, dann würdest du natürlich isoliert diese Tests ausführen und dann wären die Zeiten wieder vergleichbar.
Könnte natürlich auch eine schöne Übung für einen kleinen Hackathon sein oder vielleicht für einen Freitagabend.
Freitagabend, für einen Freitagnachmittag oder vielleicht mal für den ganzen Freitag.
Wenn ihr mal mit eurem Team eine lustige Sache machen wollt, warum setzt ihr euch nicht einfach mal einen Freitag in den Meetingraum, fragt euren Chef, ob der ein kleines Frühstück springen lässt, geht jeder zum Bäcker irgendwie ein bisschen Ei und so weiter holen und ja, dann bereitet einer eine Challenge vor und baut das einfach mal nach und dann Freitag 14 Uhr macht er eine Präsentation, jeder präsentiert seine Lösung.
wie oder was er gemacht hat.
Und ich glaube, das ist eine ganz spannende Thematik jetzt, wo AI im Spiel ist, weil jeder hat die gleichen Tools und dann kommt es auf die bessere Recherche an und allem drumherum.
Ich glaube, das könnte recht relativ lustig werden.
Guck mal, jetzt habe ich doch AI erwähnt, obwohl ich es gar nicht geplant hatte.
Aber hey, du hast mich drauf gebracht.
Ja, wäre wahrscheinlich spannend, was die AI daraus macht.
Wenn man den ganzen Input mal einer AI füttert und sagt, jetzt mach es noch schneller, wäre interessant, was dabei rauskommen würde.
Wäre vielleicht dann eine eigene Challenge.
Auf jeden Fall, lasst uns mal wissen, Was ihr dazu denkt, vor allem alle Java-Hater, würde mich natürlich am meisten interessieren, was ihr dazu sagt, oder wenn ihr andere Verbesserungsvorschläge habt, wie man das noch schneller machen kann, schaut mal bei uns in der Discord-Community vorbei.
Oder wenn ihr euch auch wundert, so wie ich, warum da Andi zu einem Bäcker geht, um ein Ei zu kaufen, bei uns gibt es da Brot, aber gut, was weiß ich schon in Österreich, ist Andis Sache.
Kommt vorbei bei uns in der Discord, wir freuen uns immer über ein bisschen Diskussion.
Wie gesagt, wer ein bisschen rumnerden möchte, schaut mal in die Shownote-Links.
Da findet ihr auch die AWK-Lösung in elf Zeilen und die ganzen Java-Implementierungen und etliche Blogposts, die die Top-Gewinner einfach mal auseinandergenommen haben.
Teilweise echt hart zu lesen, aber super lehrreich.
Kann ich nur empfehlen, falls ihr heute Abend noch nicht kopftechnisch ausgelastet seid.
Macht das mal.
Wie immer gilt, für Performance-Freaks kommt mal in den Discord, korrigiert mich mal oder auch den Wolfgang, falls ihr was Falsches gesagt habt.
Und jetzt würde mich mal interessieren, Was war die beste Performance-Optimierung, die ihr gemacht habt?
Lasst es uns mal wissen.
Ich glaube, das wird sehr, sehr viele Leute in der Discord-Community interessieren.
Da haben wir ungefähr 500 Leute inzwischen drin.
Und bei sowas sind sehr viele immer am Start.
Ich freue mich auf eure Submissions.
Bis bald.
Bye-bye.
Ciao.
