Mit GPUs sind oft große Performanceverbesserungen möglich. Um diese erreichen zu können, sind allerdings Kenntnisse der GPU-Architektur notwendig und wie man diese bei der Programmierung zu berücksichtigen hat. Die Programmierung von GPUs ist also immer noch "hardware-nah".
In diesem Vortrag werden deshalb die Unterschiede zwischen der GPU- und der CPU-Architektur erläutert. Die Frameworks CUDA und OpenCL werden vorgestellt. Es wird gezeigt, wie man die üblichen Patterns für Parallelität mit diesen Frameworks implementiert.
Schließlich werden die wichtigsten Optimierungstechniken und nützliche Tipps & Tricks aus der Praxis vorgestellt.
3. EINSATZ VON GPU-COMPUTING
Es locken große Speedups:
2 3 4 5 6 7 8 9 10 11 …
ProfiFortgeschrittenAnfänger
„Enabler“
4. ABER GPU-COMPUTING IST NICHT TRIVIAL
Entwickler
Kenntnisse, Lernbereitschaft, Zeit
Code
Reorganisation, Anpassung
Management
Time is Money
5. ZIEL: GRUNDLAGEN FÜR ERFOLGREICHEN EINSATZ
Grundlagen der Parallelisierung
Architektur CPU und GPU
Programmierung mit CUDA und OpenCL
Optimierung
2 3 4 5 6 7 8 9 10 11 …
Fortgeschritten
6. JÖRN DINKLA
Software-Developer und Berater
TechEd von OpenCL in Action
Konferenzen & Artikel
Auch: JVM, C++, JavaScript
jdinkla@thoughtworks.com
7. A community of passionate
individuals whose purpose is to
revolutionize software design,
creation and delivery, while
advocating for positive social
change.
11. GPGPU UND GPU-COMPUTING
GPGPU
Generall Purpose computing on GPUs
2003 – 2008 ein „hack“
GPU-Computing
2007 erste Version von CUDA
2008 von OpenCL
2012 C++ AMP
12. FRAMEWORKS IM ÜBERBLICK
Device
Driver
C ++ 11
C ++
C
CUDA
Runtime
C++ AMP
MS
DirectX
AMDNVIDIA
Thrust
C++-
Wrapper
OpenCL
Boost
Compute
Intel
AMD
CUDA
Driver
OpenACC
13. VOR- UND NACHTEILE
CUDA
+ Verbreitung + C++ 11 - nur NVIDIA
OpenCL
+ Offen - C99 - Nicht so viele Libraries
C++ AMP
+ C++ 11 - DirectX - geringe Verbreitung
14. VERSIONEN DES APIS
CUDA
8.0 Pascal, Unified Memory
7.5 Lambdas, 7.0 C++
OpenCL
2.2 C++ Kernel (Zukunft)
2.1 SPIR-V, Dynamic Parallelism
15. VERSIONEN DER HARDWARE
Unterschiede zwischen Karten-Generationen
z. B. NVIDIA
Pascal: Unified Memory incl. Management
Maxwell: Dynamic Parallelism für alle
Kepler: Unified Memory
16. VERSIONEN VON OPENCL HARDWARE
Beispiel Intel®SDK 2016 R3
Siehe https://software.intel.com/file/529975/download
17. FÜR WELCHE API UND HARDWARE SCHREIBT MAN?
Am einfachsten: Für jeweils die Neueste
Neue Hardware preiswerter als Entwickler
Aber Kunden mit älteren Karten?
Maintenance-Probleme später berücksichtigen
28. ARITHMETISCHE INTENSITÄT
for y = 0 to n-1
for x = 0 to n-1
int i = y*w+x
d[i] = f(s[i]);
𝑎𝑖 =
𝑅𝑒𝑐ℎ𝑒𝑛𝑜𝑝𝑒𝑟𝑎𝑡𝑖𝑜𝑛𝑒𝑛
𝑆𝑝𝑒𝑖𝑐ℎ𝑒𝑟𝑧𝑢𝑔𝑟𝑖𝑓𝑓𝑒
29. ARTEN DER AUSLASTUNG
Computation Bound
Alle Prozessoren 100% ausgelastet
Memory Bound
Bandbreite zum Speicher voll ausgelastet
Latency Bound
Warten auf die Daten
30. AUFBAU EINES COMPUTERS
CPU 1
Core
L1 / L2
Core
L1 / L2
L3 Cache
Memory Controller PCIe
Global Memory Devices
CPU 2
Core
L1 / L2
Core
L1 / L2
L3 Cache
QPI
Memory ControllerPCIe
Global MemoryDevices
≈25 ≈14
≈26
Bus ist „von-Neumann-Flaschenhals“
≈50
≈100
Transparent für
Programmierer
31. OPTIMIERUNG DER CPU
Clock Frequency
Memory Hierarchy / Cache
Parallelizing ALU
Pipelining
Very-long Instruction
Words (VLIW)
Instruction-Level
parallelism (ILP)
Superscalar processors
Vector data types (SIMD)
Multithreaded
Multicore / Manycore
33. NVIDIA PASCAL
60 SMs á 64 Kerne á 32 Bit
3840 Kerne
4 MB L2 Cache
1080: 2560 Kerne
1080 Ti: 3584 Kerne
https://devblogs.nvidia.com/parallelforall/wp-content/uploads/2016/04/gp100_block_diagram-1-624x368.png
34. AMD FIJI
64 CUs á 64 Kerne á 32 Bit
4096 Kerne
2 MB L2 Cache
Siehe http://www.anandtech.com/show/9390/the-amd-radeon-r9-fury-x-review/4
35. AUFBAU DER GPU
CPU 1
Core
L1 / L2
Core
L1 / L2
L3 Cache
Memory Controller PCIe
Global Memory Devices
≈25
≈ 14
≈50
≈100
≈ 14
GPU
Global Memory
Constant Texture
Prozessor (SM, CU)
Shared / L1
Registers
C
L2 Cache
C
C
C
C
C
C
C
C
C
C
C
C
C
SM
SM
SM
SM
SM
SM
SM
SM
SM
SM
≈ 8000
≈ 1600
≈ 320 - 700
Schneller Device-Speicher
Daten-Lokalität
45. SIMT - SINGLE INSTRUCTION MULTIPLE THREADS
Nur ein PC für Warp
if (threadIdx.x < 2) {
expensive_function1();
} else {
expensive_function2();
}
0 1 2 3
Divergenz
46. SIMT - SINGLE INSTRUCTION MULTIPLE THREADS
Bei sequentiellen Programmen (Single-Thread)
𝑇 = max(𝑇𝑖𝑓, 𝑇𝑒𝑙𝑠𝑒)
Laufzeit bei SIMT
𝑇 = 𝑇𝑖𝑓 + 𝑇𝑒𝑙𝑠𝑒
47. AUSWIRKUNG VON DIVERGENZ
Wenn divergente Threads vorhanden sind
Kerne sind idle
SM wird nicht ausgelastet
Berechnung dauert länger als notwendig
Daher Divergenz vermeiden
52. LATENCY HIDING
Speicherzugriffe sind langsam
Durch Berechnungen „überdecken“
Immer genügend Warps „in flight“ haben
DER Grund für gute Performance der GPU
Memory-
Flaschenhals
überwunden
W0SM W1 W2 W3 W4 -- -- --
Occupancy
5/8
53. WARUM NICHT IMMER 100% OCCUPANCY?
Platz auf SM beschränkt
Warps auf SM teilen sich
Shared-Memory
L1-Cache
Register-Speicher
GPU
Global Memory
Constant Texture
Prozessor (SM)
Shared / L1
Registers
C
L2 Cache
C
C
C
C
C
C
C
C
C
C
C
C
C
SM
SM
SM
SM
SM
SM
SM
SM
SM
SM
62. X-Y- STATT Y-X- ZUGRIFF
CPU „hintereinander“: for y … for x …
GPU „nebeneinander“: for x .. for y …
Gut für CPU Gut für GPU
63. ARRAY OF STRUCTURES VS. STRUCTURE OF ARRAYS
Siehe Stroustrup, „C++ Prog. Language“, 4th ed., p. 207
mem 0 1 2 3 4 5
AoS value 1 2 3 4 5 6
SoA value 1 3 5 2 4 6
64. AOS - ARRAY OF STRUCTURES
mem 0 1 2 3 4 5
AoS value 1 2 3 4 5 6
SoA value 1 3 5 2 4 6
0 1 20 1 2
Verschwenden Bandbreite
65. SOA - STRUCTURE OF ARRAYS
mem 0 1 2 3 4 5
AoS value 1 2 3 4 5 6
SoA value 1 3 5 2 4 6
0 1 2 0 1 2
Nebeneinander gut
75. SMOOTHING-ALGORITHMUS
for all pixel at x,y in image
sum = 0, c = 0
for all dx,dy in neighborhood
sum += e[x+dx, y+dy]
c += 1
a[x,y] = sum / c
𝑎𝑖 =
𝑅𝑒𝑐ℎ𝑒𝑛𝑜𝑝𝑒𝑟𝑎𝑡𝑖𝑜𝑛𝑒𝑛
𝑆𝑝𝑒𝑖𝑐ℎ𝑒𝑟𝑧𝑢𝑔𝑟𝑖𝑓𝑓𝑒
76. OPTIMIERUNG
Ermittle das Bottleneck mit Profiler
Verwende Amdahls Gesetz für Abschätzung
Siehe https://devblogs.nvidia.com/parallelforall/cuda-8-features-revealed/
81. MÖGLICHKEITEN FÜR PARALLELITÄT
CPU 1
Core
L1 / L2
Core
L1 / L2
L3 Cache
Memory Controller PCIe
Global Memory Devices
GPU
Global Memory
Constant Texture
Prozessor (SM)
Shared / L1
Registers
C
L2 Cache
C
C
C
C
C
C
C
C
C
C
C
C
C
SM
SM
SM
SM
SM
SM
SM
SM
SM
SM
CPU || GPU
Cores
Calc || Mem
SMs
Cores
HT
Kernel || Copy
82. MAXIMIERE PARALLELITÄT
Auslastung der GPU
Parallele Kernel, Streams
Auslastung der SMs
Thread-Block, Occupancy
Auslastung der Kerne
„Instruction level parallelism“ (ILP)
87. NOCHMAL: DIE UNTERSCHIEDE
1. Daten-Lokalität
2. Divergenz
3. Occupancy – Latency Hiding
4. Coalescing von Speicherzugriffen
88. METHODIK
Entwickle
1. „Golden Code“ sequentiell und korrekt
2. Parallelisiere auf CPU
3. Parallelisiere und transformiere zu GPU-Code
Unit-Tests stellen Korrektheit sicher
----------------------------------------------------------------------------------------->
Grundkenntnisse Fortgeschritten 1 jahre. 2 Jahre. 5 Jahre
"laws of diminishing returns"
"geringerer Ertragszuwachs"
"viel hilft nicht immer viel", 80/20 Regel
Tal der Tränen
"Herausforderungen" "Challenges"
Was muss man meistern? Welche Gefahren drohen?
Programmieren
C++ / Fortran
Grundkenntnisse CUDA Speedup
Fortgeschrittene Kenntnisse hoeherer
Aber Zeit und Lern-"Investition" der Entwickler
- nicht alle wollen viel lernen und sich jahrelang damit beschaeftigen
Lerninvestition (Zeit, Wissen, Lernen) ist hoch
Versuch: GPU Computing einfacher zu machen:
Dynamic Memory, OpenACC, C++ (auch in OpenCL), Kernel in C++
Technische Skills vs. Business Anforderungen
Fallbeispiele fuer GPU-Computing: der Business-Case
Tradeoff
Kenntnisse
Optimierung
----------------------------------------------------------------------------------------->
Grundkenntnisse Fortgeschritten 1 jahre. 2 Jahre. 5 Jahre
"laws of diminishing returns"
"geringerer Ertragszuwachs"
"viel hilft nicht immer viel", 80/20 Regel
Herausforderungen" "Challenges"
Was muss man meistern? Welche Gefahren drohen?
Programmieren
C++ / Fortran
Grundkenntnisse CUDA Speedup
Fortgeschrittene Kenntnisse hoeherer
Aber Zeit und Lern-"Investition" der Entwickler
- nicht alle wollen viel lernen und sich jahrelang damit beschaeftigen
Lerninvestition (Zeit, Wissen, Lernen) ist hoch
Versuch: GPU Computing einfacher zu machen:
Dynamic Memory, OpenACC, C++ (auch in OpenCL), Kernel in C++
Technische Skills vs. Business Anforderungen
Fallbeispiele fuer GPU-Computing: der Business-Case
Tradeoff
Kenntnisse
Optimierung
----------------------------------------------------------------------------------------->
Grundkenntnisse Fortgeschritten 1 jahre. 2 Jahre. 5 Jahre
"laws of diminishing returns"
"geringerer Ertragszuwachs"
"viel hilft nicht immer viel", 80/20 Regel
Nicht das Rad neu erfinden
„Structured Parallel Programming“
Michael McCool et. al.
Eher nicht für GPU-Entwickler!
Patterns, „best practices“, Einheitliche Sprache, Klärung, dann muss man das Rad nicht immer neu erfinden
„best practices“
„think parallel“
Map, Stencil
Reduktion, Scan
Gather, Scatter, Pack, Split, Expand
Flynns Taxonomy
To embarass s.o. – jemanden in Verlegenheit bringen, peinlich
Ein bischen dezenter in der Farbgebung
Anekdote:
p21 n/2 Prozessoren, TI, mit GPUs nicht mehr so abwegig
Nicht parallelisierbare Teile
Nicht parallelisierbare Teile
Parallelität maximieren!
Am besten im gesamten System
„Law of diminishing returns“
Viel hilft nicht immer viel
Wo sind die Performance-Probleme?
Präprozessor
#pragma omp parallel
Modifiziert Code
Teilt Schleifen auf in mehrere Teile
Thread-Pool, schwergewichtige Threads
Wo sind die Performance-Probleme?
GB/s
http://www.sysprobs.com/how-to-check-processor-cpu-cache-memory-in-windows-10-8
TODO Rechenbandbreite 4Ghz CPU
R9 Fury X
Fiji Hawaii Tonga
GB/s
cudaMallocManaged()
Speicher wird auf Host und Device angelegt
Ab CUDA 8.0 und Pascal-Karte
Beliebige Größe (Swapping automatisch)
Vorher ab CC 3.0
Muss in Device-Speicher passen
Thread-Block-Größe
Vielfaches von 32/64
Wenn
Kernel sehr viel Register für Variablen benötigt
oder viel Shared-Memory benutzt
dann passen nur weniger „drauf“
Muss für jeden Kernel bestimmt werden
Traditionellerweise lesen wir von rechts nach links. Dann liegen die Daten von zwei benachbarten Threads aber „übereinander“ und nicht „nebeneinander“. Deshalb sollten Threads von unten nach oben lesen. Dann werden die Zugriffe „coalesced“, d.h. in einem Schritt ausgeführt.
Co alesco "grow up", alescere
Was ist besser?
„it depends“
Hängt von Zugriffsmuster ab
In der Regel auf der GPU aber SoA
„Ein weites Feld“,
Daumenregel: Überall da, wo es C gibt, gibt es auch Optimierungsmöglichkeiten
Für jeden Voxel: Berechne den Durchschnitt
Laufzeit N^3 * W^3
Parallelisierung: Einfach, da Berechnung unabhängig
x*y*z Threads
Wichtig: Work per Datum, Granularität
nvprof.exe, Nsight, NVVP
Nur „Release“-Code profilen
„Debug“-Code ist nicht repräsentativ
Code wird u. U. sehr oft ausgeführt
Keine großen Beispiele verwenden
Auch nicht zu klein
2 wegen Fused-Multiply-Add
Basis Takt
Hardware-Kenntnisse erforderlich
Hardware
Bandbreiten, Overlap
#Prozessoren und Threads
Compute Capability
Runtime/Driver API Version
TODO schrift
GPU‘s ergänzen die CPU, nicht ersetzen!
Typische Anwendung ist eine Schleife
for all partitions
call kernel 1
call kernel 2
…
Wenn man den obigen Code dreimal hintereinander ausführt
Schlechte Auslastung der Grafikkarte
Nur visuell angedeutet, nicht mathematisch exakt
Mehr dazu später bei der Optimierung
WICHTIG: Anordnung / Reihenfolge des Codes
Hier verfügen die Karten über unterschiedliche Möglichkeiten, manche können auch ein H2D und D2H gleichzeitig ausführen.
Vorher immer eine synchrone Version erstellen
100% funktionstüchtig und korrekt
Dann die Synchronisation umsetzen
Asynchronizität ist verwirrend
TODO umordnen
Minimiere Kopien zwischen Host und Device, Besser eine große als viele kleine
Shared Mem
Bankkonflikte
Constant Memory
Nur bei gleichem Index im Warp
Register
Read-after-write
#pragma unroll
int in for-Schleife
wg. Optimierung
unsigned int hat Overflow-Checks
Arithmetik
Float statt Double
Fast Math-Kompilieroption
„Intrinsic“-Funktionen
Divisionen vermeiden
__functionName() versus functionName()
Fast path vs. Slow path
Größe von x
sincosf()
x*x*x statt pow(x, 3)
(*) Unterschiede CPU-GPU
----------------------------------------------------------------------------------------->
Grundkenntnisse Fortgeschritten 1 jahre. 2 Jahre. 5 Jahre
"laws of diminishing returns"
"geringerer Ertragszuwachs"
"viel hilft nicht immer viel", 80/20 Regel