HPC Crashkurs - Mathematik, TU Dortmund

Werbung
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/
Herunterladen