JAX 2009: Fette Maschinen brauchen schlanke Software

(Disclaimer: this text was written while listening to the presentation – please be forgiving with errors that might result from both listening and writing)

Dr. Klaus Alfter präsentiert eine Keynote zum Thema “Wie gehen wir mit der stark wachsenden Zahl von Cores in Systemen um, die sich gar nicht so leicht in Performance-Zuwächse ummünzen lassen”.

Als erstes weist er darauf hin, dass Amdahls Law dazu führt, dass selbst bei sehr hoher Parallelität (z.B. 99%) Performance-Gewinne in nur vergleichsweise geringem Maß zu erwarten sind (bei 16 Cores immerhin noch eine 14fache Geschwindigkeitssteigerung, bei 256 Cores gerade mal noch eine 72fache Steigerung – mit sinkender Tendenz und steigender Komplexität in der Parallelisierung von Algorithmen).

Das Grundproblem geht auf von-Neumann-Rechner zurück: Speicherzellen werden immer wieder überschrieben und daher müssen konkurrierende Bibliotheken synchronisiert werden. Synchronisation kostet aber Zeit, weshalb die meisten Frameworks eine sequentielle Abarbeitung annehmen, um das Laufzeitverhalten durch Weglassen von Synchronisation zu verbessern.

Dann beschäftigt er sich mit verschiedenen eingebildeten Annahmen. 

Erste Illusion: Nebenläufigkeit im Java-Enterprise-Umfeld ist doch ein alter Hut. Ist natürlich Unsinn, da viele Programmierungen sequentiell ablaufen. Beispiel: Initialisierung eines Arrays mit dem Wert 0 für alle Felder – dies ist eine parallel ausführbare und reihenfolgenunabhängige Aktivität, die ein Compiler bei Verallgemeinerung nicht erkennen kann und die aufgrund der Unentscheidbarkeit des Halteproblems nicht entscheidbar sind. Mit anderen Worten: Es wird niemals eine Maschine geben, die diese Sachverhalte erkennen und uns bei der Parallelisierung von Algorithmen nachhaltig unterstützen kann.

Virtualisierung ist sicher ein Ausweg, hilft aber nur bedingt, da damit zu viele nicht wirklich ausgelastete Systeme geschaffen werden.

Das nächste Problem sind die Latenzen, die zunehmend durch Signallaufzeiten z.B. bei Speicherzugriffen entstehen. Das flache Java-Speichermodell ist hier wieder keine Unterstützung.

John Backus, der Erfinder von Fortan, hat sich konsequenterweise bereits seit 1978 mit der Frage beschäftigt, ob es nicht einen Weg gibt, sich vom von-Neumannschen-Stil zu befreien. Untrügliche Zeichen davon, dass sich Backus’ Ideen langsam durchsetzen sind Indikatoren wie der Scala-Einsatz bei Twitter oder die Implementierung von CouchDB in Erlang

Drei wesentliche Ansätze betrachtet Alfert kurz weiter:

  • Funktionale Programmierung
  • Transactional Memory
  • Aktoren

Funktionale Programmierung geht von der Annahme aus, dass eine mathematische Funktion ohne Seiteneffekte und globalen Zustand nur über ihre Parameter eine Berechnung durchführt. Die Vorteile funktionaler Probleme fallen unmittelbar beim Unit-Testen auf, da man keine komplexen Testkontexte aufbauen muss. Anhand diverser in Haskell formulierter Beispiele verdeutlicht Alfert die Vorteile funktionaler Programmierung udn weist auch noch mal darauf hin, dass Piping in Shells und SQL auch gute Beispiele für nebeneffektsfreie Operationen sind. Er verfeinert seine Ausführungen dann dadurch, dass er darauf hinweist, dass es eben doch Seiteneffekte gibt, z.B. für Synchronisation oder das Schreiben von Daten. Aber es gibt eben keinen globalen Zustand. Statistisch gesehen braucht man Alferts Erfahrung nach scheinbar nur in 10% der Fälle bei Haskell-Programmen tatsächlich Seiteneffekte, was natürlich hoffen lässt.

Ein zweites noch sehr forschungslastiges Konzept sind Transaktionen, wie man sie klassisch insbesondere aus Datenbanken kann. Alferts eingängige abstraktere Sicht ist, eine Datenbank als globale Variable zu betrachten, die vor dem commit gesichert wird. In kleinerem Maße wird dies als Transactional Memory bezeichnet, bei dem nach einer Berechnung geprüft wird, ob durch parallele Zugriffe ein Restart einer Berechnung erforderlich wird. Schwierig wird’s hier natürlich bei Seiteneffekten, bei dem sich z.B. Haskell durch Definition eines besonderen Zustandstyps behilft, in denen z.B. keine IO-Zustandsveränderungen stattfinden dürfen, wenn dieser Zustand verwendet wird. Hier wird für C++ und Java aber noch intensive geforscht, um entsprechende Eigenschaften auszuprägen.

Als Drittes führt er Aktoren an, die Objekten ähneln, in dem sie Zustand und Verhalten haben und auch auf Nachrichten reagieren. Aber sie laufen als großem Unterschied in eigenen Threads und sind daher autonom.  Scala bietet gegenwärtig als hybride auf JVM basierende Sprache die beste Unterstützung für dieses Konzept. Dort kann man es schaffen, bis zu einer Million paralleler Aktoren auf einer JVM laufen zu lassen – mit Threads wäre hier lange vorher bei ca. 4000-5000 Threads Schluss. Aktoren gehen wiederum auf Erlang und die Arbeiten von Ericsson in diesem Umfeld zur Konfiguration von Telekommunikationsanlagen zurück.

Ein Beispiel aus der Enterprise-Welt ist die Realisierung von COMET-basierten Anwendungen (wie z.B. Online-Auktionen) mit Aktoren. Da jeder Aktor seinen eigenen HEAP hat, werden hier viele Synchronisationsaufgaben entfallen.

Ein weiterer Vorteil der auf diese Weise erhöhten Parallelität kann auch die Robustheit von Systemen erhöht werden, da sie Möglichkeiten bieten, parallel laufende Prozesse zu überwachen und bei Problemen neu zu starten.

Alfert schließt seine Ausführungen mit der Beobachtungen, dass sich unser Programmierverhalten grundlegend ändern müssen, damit unsere Algorithmen in 10 Jahren noch zu der Hardware passen, die dann im Einsatz sein werden.

Ich sag’s mal mit den Chinesen: “Wir leben in interessanten Zeiten” 🙂

Comments are closed.