Wissenschaftliches Rechnen in der Praxis: Hardware und Hardware-orientierte Programmierung Dominik Göddeke [email protected] Vorlesung Wissenschaftliches Rechnen Fakultät für Mathematik 29. Juni 2010 Hinweise • Folien • http://www.mathematik.tu-dortmund.de/~goeddeke/teaching/ • Prüfungen • Nicht nur die VL von Prof. Turek sind prüfungsrelevant • In den Nicht-Turek VL zählen Konzepte und Ideen und nicht technische Details • Wer Code-Details auswendig lernt ist selbst schuld ☺ Wo waren wir? • Die Memory-Wall-Problematik • Daten zu lesen und zu schreiben ist viel teurer als Berechnungen mit ihnen durchzuführen • Abstand zwischen Rechengeschwindigkeit und Speicherbandbreite wird immer größer • PDE-Codes sind fast immer bandbreitenlimitiert • Rechnen: 60% schneller pro Jahr • Speichertransfers: 10-30% schneller pro Jahr, je nachdem, welche Ebene der Speicherhierarchie Beispiel (vereinfacht aber instruktiv) • Addition zweier Vektoren der Länge n (zu lang für den Cache) • Rechenoperationen: n, Speicheroperationen: 3n, 8 Byte pro Wert • Arithmetische Intensität: 1/3 • Mein Rechner: 12 GFLOP/s Rechnung, 10 GB/s Speicherbandbreite • Um 12 GFLOP/s zu sehen, bräuchten wir (1/3)^-1 * 8 * 10 GB/s = 0.24 TB/s Speicherbandbreite • D.h. 24 mal so viel wie wir haben • Vergleich: 2/3 der Festplatte in meinem Laptop pro Sekunde kopieren • Andersherum argumentiert • Wir können maximal (Sonntags, bei gutem Wetter und bergab) nur 12/24 = 0.5 GFLOP/s für diese Operation erreichen! • D.h. 4.2% der theoretischen Maximalleistung • Memory wall: Dieser Prozentsatz wird immer kleiner CPUs und Speicherhierarchie (off-chip) • • Northbridge-Southbridge Architektur (oder auch: von-Neumann-Arch.) • Gängige Praxis in „normalen“ PCs und Workstations • Und deshalb auch in Clustern die aus „commodity HW“ zusammengesetzt sind • LiDOng: Multiprozessor-System, aber kein prinzipieller Unterschied • Idee: Standardisierte Komponenten • Jede CPU arbeitet (im Prinzip) mit jedem Speicher und jeder Grafikkarte jeden Herstellers zusammen Latenz und Bandbreite • Latenz: Wie lange dauert es, ein Datum zu holen? • Gemessen typischerweise in Nano- oder Mikrosekunden (10^-9-10^-6 Sekunden) • Absolute Zahlen sind wenig hilfreich. Schlauere Referenzwerte: • CPU-Takt (CPU in meinem Rechner: 2.66 GHz, d.h. 2,66 ns pro Takt) • Latenz von 10 μs entspricht 10.000/2,66 = 3760 Takten • Maximale Fliesskomma-Berechnung (meine CPU: 10 GFLOP/s, d.h. 0.1 ns pro Operation) In Wirklichkeit 12 GFLOP/s aber egal ☺ • Latenz von 10 μs entspricht 10.000/0.1 = 100.000 Fliesskommaoperationen • Simplistisch: Diese „Bierdeckel-Rechnungen“ setzen voraus, dass die Daten, die für diese Rechnungen nötig sind, in Nullzeit zugreifbar sind • Aber: Es geht hier im Moment um ein Gefühl für Größenordnungen Latenz und Bandbreite • Bandbreite: Wie viele Daten kann ich in gegebener Zeit tatsächlich transferieren? • Auch: (Speicher-) Durchsatz • Klar: Man transferiert nicht jedes Datum einzeln, deshalb ist Bandbreite nicht dasselbe wie Latenz • Gemessen typischerweise in GB/s • Memory wall hat zwei Aspekte: Latenz und Bandbreite • Daumenregel: Latenz = kleine Datenmengen, Bandbreite = große Datenmengen • Für uns ist üblicherweise Bandbreite relevanter Latenz und Bandbreite • Latenz minimieren in Hardware • Über lange Distanzen (von PC zu PC): GHz-Takte in der Rechnung implizieren schon physikalische Grenzen (Lichtgeschwindigkeit!) bei der Signallaufzeit • Lange bis mittlere Distanzen: Daten müssen durch viele verschiedene Instanzen laufen (Ethernet – Netzwerkkarte – Southbridge – Northbridge – CPU), jede Instanz bremst • Bandbreite maximieren in Hardware • Technischer Hintergrund: Busse • Mehrere Datenwege (Leitungen) parallel • Viele Transfers können gleichzeitig über einen Bus / eine Leitung / ein Kabel laufen, wenn der Sender sie schnell genug auf den Bus packen kann und/oder der Empfänger sie schnell genug wieder auslesen kann • Multicore: Viele Speichertransfers zu verschiedenen Zielen gleichzeitig • Das war die Elektrotechnik-Folie für heute ☺ Beispiel: Latenz Netzwerk • Ethernet PC zu PC • Gigabit Ethernet (mein PC) • Latenz • ~150 μs = 0.00015 s (um eine Nachricht von Hilmar‘s PC zu meinem PC zu schicken) • Bei 10 GFLOP/s theoretischer maximaler Rechenleistung entspricht dies ca. 1.5 Millionen Fliesskomma-Operationen • Supercomputer: Infiniband statt Ethernet • Alle Ethernet-Varianten haben praktisch dieselbe Latenz • Infiniband 10-50fach kleiner • Hausnummern: gigE-Hardware kostet 1 EUR (auf Mainboard), 10 EUR (als PCI-Karte); IB-Karte kostet 500 EUR; Preise für Kabel und Switches dazu proportional Beispiel: Latenz Hauptspeicher • Genaue Werte sehr variabel • Aber: 10 ns ist ein guter Daumenwert für aktuelle Speichermodule • Je nach Mainboard/RAM-Hersteller und Mut des Übertakters: 8-16 ns • Vergleich mit Netzwerk • 150.000/10 = 15.000 mal bessere Latenz als gigE (!!!) • Keine Überraschung, nur die Größenordnung beeindruckt • Vergleich mit CPU • 10 / 0.1 = 100 bei 10 GFLOP/s • 100 Operationen pro Latenz eines Datums Beispiel: Bandbreite • Bandbreite von gigE • 1000 Mbit/s = 125 MB/s theoretisch • Bandbreite von Infiniband • 1-4 GB/s theoretisch (SDR IB zu QDR IB) • Unterschied zu Ethernet erklärt die höheren Kosten um HW zu kaufen • Bandbreite des Hauptspeichers • 10-40 GB/s je nach aktueller Architektur • Mein Rechner: 10 GB/s (Stand 2008) • Neue Nehalem-Knoten in LiDOng: 33 GB/s Zusammenfassung (off-chip) • Latenz • Speicher reagiert Faktor 1.00015.000 mal schneller als das Netzwerk • Bandbreite (wichtig) • Faktor 10-100 zwischen Netzwerk und Speicher • Wenn genug transferiert wird so dass Latenz ignoriert werden kann • Aktueller Schnappschuss • Tatsächliche Abstände werden immer größer • Relevant sind Größenordnungen, und die bleiben mehr oder weniger konstant Jetzt: on-chip • Beispiel: AMD Athlon64 CPU (2004, echtes Die-Foto) Jetzt: on-chip • Beispiel: Intel Core2 (mein PC, 2008, echtes Die-Foto) Chipfläche einer CPU • Cache • • • • • 50-60% der Chipfläche Level 1 Cache (L1 Instruktionen und Daten) Level 2 Cache (L2 Daten) Manchmal auch: L3 Cache (on-chip oder off-chip aber dedizierte Leitungen) Kontrollfluss und HW-Heuristiken (Branch Prediction etc.) • 30-40% der Chipfläche • Rechnen • Weniger als 10% der Chipfläche (inklusive Register) • Verhältnisse stimmen auch noch für Multicore-Chips CPUs: Rechnen • Was passiert in den 5-10% der CPU, die tatsächlich rechnen? • Multicore wird wegabstrahiert (sonst ist das folgende proportional zur Anzahl Kerne) • Generische FPU (floating point unit) • Typischerweise 80 bit (mehr als double!) • Wird immer dann benutzt, wenn wir oder der Compiler das nicht verbieten (-O0 aktiviert manchmal die generische FPU, es wird statt mit einfacher mit mehr als doppelter Genauigkeit gerechnet, anderes Ergebnis! Mehr dazu nächste Woche) • Eine Operation pro Takt CPUs: Rechnen • SSE-Einheiten (streaming SIMD, früher MMX) • Können SIMD auf 4 Daten (einfach genau Fließkomma) oder 2 Daten (doppelt genau Fließkomma) • Also 2 oder 4 Operationen pro Takt • Erinnerung: SIMD • Short-Vector Instruktionen, bspw. Addition zweier Viertupel • Bedingung: Alignment • Daten müssen konsekutiv im Speicher abgelegt sein • Damit auch das Laden in die SIMD-Register in einem Takt abläuft • Register • Speicher direkt neben den Recheneinheiten, schnellste Zugriffszeit Caches • Memory wall • Caches sind das #1 Hardware-Mittel der Wahl, um sowohl dem Latenz- als auch dem Bandbreiten-Aspekt der Memory Wall Problematik entgegenzuwirken • Caches halten Daten in schnellem Speicher vor, in der Hoffnung, dass sie schnell wieder genutzt werden • Caches sind on-chip und haben deshalb sehr kurze Signallaufzeit zu den Rechen-Einheiten • Caches sind moderat einfach in Hardware zu bauen • Tradeoff: Verantwortlich für den hohen Energiebedarf von CPUs (aber das führt in dieser VL zu weit) • Beispiel: Matrix-Vektor Multiplikation • Matrix cachen bringt nichts (jedes A_ij wird nur einmal benötigt) • Koeffizientenvektor cachen bringt viel (wird einmal pro Matrixzeile benötigt) Endlich: Speicherhierarchie Bandbreite Größe Anderer Rechner: 1 GB/s Anderer Rechner: 8 GB Hauptspeicher: 10 GB/s Hauptspeicher: 8 GB L2 Cache: 100 GB/s L2 Cache: 1-4 MB L1 Cache: 1000 GB/s L1 Cache: 32-64 kB Register: Nullzeit Register: wie L1 Daumenregel: eine Größenordnung pro Hierarchieebene Daumenregel: drei (!) Größenordnungen pro Hierarchieebene Konsequenzen in der Praxis Lokalität • Lokalität • Wichtigstes Mittel um die Speicherhierarchie effizient nutzen zu können • Lokalität = locality of reference • Räumliche Lokalität • Daten, die nebeneinander im Speicher liegen sollten auch direkt nacheinander verarbeitet werden • Zeitliche Lokalität • Rechne möglichst lange auf Daten, die möglichst nah am Prozessor liegen • Problem: Je näher wir an den Prozessor kommen, desto weniger Speicher steht zur Verfügung Caches • Entscheidung der Hardware • Welche Daten wann wo in den Caches liegen ist eine Entscheidung der Hardware • Hardware stellt Konsistenz der gecachten Daten zum Hauptspeicher sicher (Cache-Kohärenz) • Hardware analysiert den Datenstrom und kann Daten asynchron zur Rechnung vom Speicher in die Caches verschieben (prefetching) • Heuristik: Ob wir die Daten tatsächlich verwenden ist wissen natürlich nur wir • Block-Transfers • Hardware holt oft ganze zusammenhängende Datenblöcke in den Cache („Cache-Zeile“) • Diese Speichertransfers sind sehr effizient Datenlayouts • Gute Implementierung • Verwende Datenlayouts und Datenzugriffsmuster, die es dem Compiler erlauben, die Speicherhierarchie effizient zu nutzen • Maximiere Wiederverwendung von Daten • Beispiele gleich • Speicherhierarchie und SSE • Lustigerweise sehr eng verbunden: Datenlayouts die Lokalität maximieren können typischerweise vom Compiler gut in (viel effizienteren) SSE-Code übersetzt werden Datenlayouts • Datenlayout für verschiedene Varianten der gleichen Operation • Sparse Matrix-Vektor Multiplikation springt potentiell wild im Koeffizientenvektor herum • Sparse Matrix-Vektor Multiplikation greift kontinuierlich auf die Matrix zu • Mehr dazu von Hilmar morgen • Optimales Layout für verschiedene Operationen • Kompromiss: Layout das optimal ist für eine Operation muss nicht optimal sein für eine andere Datenlayouts • Beispiel: Red-Black Gauß-Seidel (Parallelisierungstechnik) • Optimales Layout füt Vektor-Vector Operationen: kontinuierliche Nummerierung • Sorgt für „Löcher“ in der Anwendung eines Gauß-Seidel Schritts Compiler vs. Programmierer • Compiler sind prinzipiell doof • Ein guter Programmierer weiß aber, wann er sich auf den Compiler verlassen kann • Verschiedene Compiler sind unterschiedlich doof: Benutze die IntelCompiler für Zeitmessungen und die GNU-Compiler für Debugging • Compiler sind sehr gut (gut genug für uns) auf der Ebene der Register • Explizites Placement auf Registerebene ist in der Regel kontraproduktiv • Assembly-Language-Programmierung bringt kaum zusätzlichen Gewinn Compiler vs. Programmierer • Compiler sind sehr gut für lineare Array-Zugriffe • Einfach in en-bloc Transfers übersetzbar • Vec-Vec-Operationen • Compiler sind manchmal gut in SSE • Wenn die Indizierung kontinuierlich sind • Beispiel: Vektor-Addition von vor 20 Folien liest alle Elemente kontinuierlich • Dann optimale Nutzung der effektiven Speicherbandbreite • Compiler sind sehr schlecht in SSE • Wenn die Speicherzugriffe nicht kontinuierlich sind • Und insbesondere wenn wild im Speicher herum gehüpft wird • Mehr dazu im Kontext dieser Vorlesung morgen bei Hilmar und im folgenden Compiler vs. Programmierer • Wichtigste Regeln zusammengefasst • Numerische Effizienz ist das wichtigste • Ein für den Menschen gut lesbarer Code ist in der Regel auch für den Compiler gut optimierbar, aber: • Datenlayout, Datenzugriffsmuster und Algorithmen müssen an die Speicherhierarchie angepasst werden (Beispiele gleich) Techniken zur Steigerung der Lokalität Datenlayout • Benutze ein geeignetes Datenlayout • Der „Klassiker“ bei 2D-Arrays • C/C++/Java: Row-major • Fortran/Matlab: Column-major Loop Interchange • C-Stil für 2D-Arrays impliziert andere Schleifenordnung als FortranStil, Lösung: Schleife an Datenlayout anpassen Loop Fusion • Vermeide teure, überflüssige Umwege über den Hauptspeicher wenn irgendwie möglich • In der Praxis oft kompliziert, gerade wenn MatVec- und Skalarprodukte im Spiel sind • Deshalb oft in Kombination mit numerischen Reformulierungen Loop Blocking / Tiling • Beispiel: Matrix-Transponierung Fazit • Korrektes Datenlayout ist sehr wichtig • Mindestens um dem Compiler zu erlauben, Code so gut wie möglich zu optimieren • Bisher: Kurzes Kratzen an der Oberfläche und Präsentation der wichtigsten Klassen von Verfahren um Code effizienter zu machen • Morgen mehr dazu von Hilmar für numerische lineare Algebra • Literatur: • Markus Kowarschik und Christian Weiß: „An Overview of Cache Optimization Techniques and Cache-Aware Numerical Algorithms“, in: „Algorithms for Memory Hierarchies“, Springer Lecture Notes in Computer Science (LNCS) Nummer 2625, pp. 213-232, 2003 • http://www10.informatik.uni-erlangen.de/Research/Projects/DiME/