PHPC Parallel and High Performance Computing – Erstklausur 2025
(Dies ist ein Gedankenprotokoll, das den Klausurinhalt versucht detailliert wiederzugeben.)
Aufgabe 1: Computational Intensity
Beschreibung:
Definieren Sie den Begriff der Computational Intensity.
Ein mögliches mathematisches Beispiel wäre:
I=Memory AccessesFloating Point Operations
Shared Memory Prints:
Geben Sie Beispiele, in denen verschiedene Threads ihre Ausgaben (Prints) erzeugen.
Erklären Sie, in welcher Reihenfolge die einzelnen Threads (z. B. Thread 1, Thread 2, …) ihre Ausgaben produzieren.
Diskutieren Sie, wie die Reihenfolge der Prints durch Synchronisation oder Scheduling beeinflusst wird.
Aufgabe 2: Cache Misses
Erklärung:
Beschreiben Sie, was Cache Misses sind, und warum sie auftreten.
Arten von Cache Misses:
Nennen und erklären Sie die unterschiedlichen Typen, z. B.:
Compulsory Misses (Kaltstarts)
Capacity Misses
Conflict Misses
Aufgabe 3: False Sharing
Definition:
Erklären Sie den Begriff False Sharing.
Behebung:
Beschreiben Sie Ansätze, um False Sharing zu beheben, beispielsweise durch Padding oder Neuordnung der Daten.
Unterschied zu True Sharing:
Diskutieren Sie den Unterschied zu True Sharing, bei dem mehrere Threads absichtlich auf dieselben Daten zugreifen, um Kommunikation zu ermöglichen.
Aufgabe 4: OpenMP
Kursinhalt:
Erläutern Sie die Grundlagen von OpenMP, wie sie im Kurs behandelt wurden.
Print Statements in verschiedenen Bereichen:
Erklären Sie, wie viele Print-Ausgaben in Parallelbereichen und in kritischen Abschnitten (Critical Sections) ausgegeben werden.
Untersuchen Sie, wie der Einsatz von Critical Sections die Ausgabe-Reihenfolge beeinflusst.
Shared Attributes:
Beschreiben Sie, ob und wie Variablen als first private, mapped, private, shared usw. deklariert wurden.
Erklären Sie deren Bedeutung und Auswirkungen auf die Parallelität.
Aufgabe 5: Numer
Befehl und Output:
Erklären Sie den Befehl:
numer ctl --hardware
und interpretieren Sie den dazugehörigen Output im Hinblick auf die verwendete Hardware.
Gegebener Code:
Ein Beispiel könnte eine Matrixmultiplikation sein, bei der für jedes Element gilt:
Cij=k∑Aik⋅Bkj
Optimierung:
Erklären Sie, wie Sie den Code „speed-uppen“ können, z. B. durch:
Schleifenoptimierung
Parallelisierung
Nutzung von SIMD-Instruktionen oder anderen Hardware-Optimierungen
Aufgabe 7: Scaling Study
Graphen Zuordnung:
Graph 1: Eine nahezu horizontale Linie (parallel zur X-Achse), die Strong Scaling darstellt.
Beispielhaftes Skalierungsmaß:
Sstrong=TpT1
Graph 2: Ein Graph, der von oben links nach unten rechts verläuft und Weak Scaling repräsentiert.
Abweichungen vom Ideal:
Erklären Sie, warum der Graph vom idealen Skalierungsverhalten abweicht und welche Faktoren (z. B. Overhead, Kommunikationskosten) dazu führen.
Ideallinien
Die dargestellten Kurven entsprechen genau dem, was man theoretisch unter „idealem“ Strong Scaling und „idealem“ Weak Scaling versteht:
Strong Scaling
Idealer Speedup (links oben): Bei n Kernen ist der ideale Speedup Sideal(n)=n. Die dargestellte Gerade, die bei 1 Kern mit S=1 beginnt und sich linear bis etwa 60 bei 60 Kernen fortsetzt, zeigt das erwartete lineare Wachstum.
Ideale Laufzeit (rechts oben): Entsprechend dem idealen Speedup ist die Laufzeit Tideal(n) umgekehrt proportional zur Kernzahl, also Tideal(n)=nTseq. Daher fällt die Kurve mit wachsender Kernzahl stark ab und nähert sich einem sehr kleinen Wert (im Diagramm sieht man, wie sie nach rechts hin Richtung 0 läuft).
Weak Scaling
Idealer Speedup (links unten): Beim Weak Scaling vergrößert man die Problemgröße proportional zur Kernzahl, sodass die Bearbeitungszeit im Idealfall konstant bleibt. Der ideale Speedup wird daher üblicherweise als 1 angegeben (man vergleicht mit demselben, aber hochskalierten Problem). Die konstante Linie bei S=1 im Diagramm passt dazu.
Ideale Laufzeit (rechts unten): Auch hier bleibt die Laufzeit im Idealfall bei Erhöhung der Kernzahl gleich, weil man zwar mehr Rechenkapazität hat, aber auch ein entsprechend größeres Problem rechnet. Die horizontale Kurve bei einer Laufzeit von 1 (bzw. einer konstanten Normierung) ist daher korrekt.
MPI: Distributed-Memory (jeder Prozess hat eigenen Speicher, Kommunikation per Nachrichten)
Programmiermodell:
OpenMP: Nutzt Compiler-Direktiven, um parallele Codebereiche zu definieren
MPI: Erfordert explizite Sende-/Empfangsoperationen zur Kommunikation zwischen Prozessen
Skalierbarkeit:
OpenMP: Ideal für Multi-Core-Systeme innerhalb eines Knotens
MPI: Hohe Skalierbarkeit über mehrere Rechner/Knoten hinweg (Cluster-Computing)
Synchronisation & Kommunikation:
OpenMP: Synchronisation über gemeinsam genutzte Variablen und Barrieren
MPI: Explizite Synchronisation via MPI-Befehle (z. B. MPI_Send, MPI_Recv, MPI_Barrier)
Fazit: OpenMP eignet sich hervorragend für parallele Programmierung auf einem gemeinsamen Speicher, während MPI die Kommunikation und Koordination zwischen Prozessen auf verteilten Systemen ermöglicht.
Korrektheit der MPI-Codeabschnitte:
Analysieren Sie, ob die gegebenen MPI-Codeabschnitte korrekt implementiert sind.
Diskutieren Sie, ob es zu Deadlocks kommen kann, und begründen Sie Ihre Antwort.
Wichtige Punkte bei der Analyse von MPI-Codeabschnitten und Deadlocks:
Send/Receive-Reihenfolge:
Sicherstellen, dass für jedes MPI_Send ein passendes MPI_Recv existiert.
Unterschiedliche Aufrufreihenfolgen (z. B. Send bei einem Prozess, Recv bei einem anderen) sollten aufeinander abgestimmt sein, um Deadlocks zu vermeiden.
Kommunikationsmodus (Blocking vs. Non-Blocking):
Blocking: Funktionen wie MPI_Send/MPI_Recv blockieren den Prozess bis zum Abschluss der Operation.
Non-Blocking: Funktionen wie MPI_Isend/MPI_Irecv starten die Kommunikation und erlauben dem Prozess, weiterzuarbeiten; eine spätere Synchronisation (z. B. mit MPI_Wait) ist erforderlich.
MPI Tags:
Numerische Kennungen zur Identifikation von Nachrichten, die es dem Empfänger ermöglichen, spezifische Nachrichten zu filtern und korrekt zuzuordnen.
Aufrufreihenfolge innerhalb der Prozesse:
Gleiche oder inkonsistente Reihenfolgen (z. B. beide Prozesse senden zuerst) können zirkuläre Abhängigkeiten verursachen und zu Deadlocks führen.
MPI-spezifische Initialisierung und Abschluss:
Korrekte Verwendung von MPI_Init und MPI_Finalize gewährleistet, dass alle Prozesse sauber starten und beendet werden.
Beispiel: Korrekte vs. Fehlerhafte Implementierung für alle kritischen Punkte
// Beide Prozesse führen MPI_Send zuerst aus, dann MPI_Recv.MPI_Send(&data, 1, MPI_INT, (rank + 1) % 2, 0, MPI_COMM_WORLD);MPI_Recv(&data, 1, MPI_INT, (rank + 1) % 2, 0, MPI_COMM_WORLD, &status);
Erklärung: In der fehlerhaften Version blockieren beide Prozesse beim Senden, da der passende Empfang erst nach dem Senden erfolgt – dies kann zu einem Deadlock führen.
2. Kommunikationsmodus (Blocking vs. Non-Blocking):
MPI_Request req;MPI_Isend(&data, 1, MPI_INT, dest, tag, MPI_COMM_WORLD, &req);// Weitere Berechnungen können hier erfolgen.MPI_Wait(&req, &status); // Synchronisation zum Abschluss der Kommunikation.
Non-Blocking – Fehlerhaft:
MPI_Request req;MPI_Isend(&data, 1, MPI_INT, dest, tag, MPI_COMM_WORLD, &req);// Synchronisation fehlt → Kommunikation wird möglicherweise nie abgeschlossen.
Erklärung: Bei non-blocking Operationen muss durch einen späteren Synchronisationsaufruf (z. B. MPI_Wait) sichergestellt werden, dass die Kommunikation beendet wird.
3. MPI Tags:
Korrekt:
// Senden und Empfangen mit übereinstimmendem Tag.MPI_Send(&data, 1, MPI_INT, dest, 100, MPI_COMM_WORLD);MPI_Recv(&data, 1, MPI_INT, source, 100, MPI_COMM_WORLD, &status);
// Fehlt MPI_Init vor der Kommunikation oder MPI_Finalize zum Abschluss.MPI_Send(&data, 1, MPI_INT, dest, tag, MPI_COMM_WORLD);// …// Kein Aufruf von MPI_Finalize()
Erklärung: MPI muss vor jeglicher Kommunikation initialisiert und nach Abschluss aller Operationen korrekt beendet werden, um Ressourcen sauber freizugeben.
Fazit: Achten Sie bei MPI-Implementierungen immer darauf, dass Send/Receive-Aufrufe korrekt aufeinander abgestimmt sind, non-blocking Operationen ordnungsgemäß synchronisiert werden, Tags konsistent verwendet werden und die MPI-Umgebung korrekt initialisiert und beendet wird.
/* Beispielcode zu Aufgabe 8: MPI Aufgabe: Analysieren Sie, ob die gegebenen MPI-Codeabschnitte korrekt implementiert sind. Diskutieren Sie, ob es zu Deadlocks kommen kann.*/#include <mpi.h>#include <stdio.h>int main(int argc, char **argv) { MPI_Init(&argc, &argv); int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); int send_data = rank; // Daten, die gesendet werden (z.B. die Prozess-ID) int recv_data = -1; // Variable zum Empfangen der Daten MPI_Status status; if (size < 2) { if (rank == 0) { printf("Dieses Beispiel erfordert mindestens 2 Prozesse.\n"); } MPI_Finalize(); return 0; } /* Codeabschnitt A: - Prozess 0 sendet zunächst, dann empfängt er. - Prozess 1 empfängt zunächst, dann sendet er. Dieser Ablauf soll einen Deadlock vermeiden. */ if (rank == 0) { MPI_Send(&send_data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); MPI_Recv(&recv_data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &status); printf("Prozess %d: Daten %d von Prozess 1 empfangen.\n", rank, recv_data); } else if (rank == 1) { MPI_Recv(&recv_data, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); MPI_Send(&send_data, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); printf("Prozess %d: Daten %d von Prozess 0 empfangen.\n", rank, recv_data); } /* Codeabschnitt B: Mögliches Deadlock-Szenario Beide Prozesse führen MPI_Send und MPI_Recv in gleicher Reihenfolge aus. Bitte analysieren Sie, ob hier ein Deadlock auftreten kann! */ if (rank == 0 || rank == 1) { MPI_Send(&send_data, 1, MPI_INT, (rank + 1) % 2, 1, MPI_COMM_WORLD); MPI_Recv(&recv_data, 1, MPI_INT, (rank + 1) % 2, 1, MPI_COMM_WORLD, &status); printf("Prozess %d: Nachrichtenaustausch abgeschlossen, empfangene Daten: %d\n", rank, recv_data); } MPI_Finalize(); return 0;}
Lösung
Zusammenfassung der Lösung
Codeabschnitt A:
Korrekt implementiert.
Die unterschiedliche Reihenfolge (Sender/Empfänger vs. Empfänger/Sender) stellt sicher, dass immer ein passender Partner für den Datenaustausch vorhanden ist, wodurch Deadlocks vermieden werden.
Codeabschnitt B:
Gefährlich hinsichtlich Deadlock:
Beide Prozesse führen zunächst MPI_Send aus. Falls beide Prozesse blockieren, weil sie auf den jeweiligen MPI_Recv warten (bei fehlendem oder unzureichendem Puffer), kann ein Deadlock entstehen.
Die Sicherheit dieses Codeabschnitts ist also implementierungsabhängig und kann nicht als generell korrekt betrachtet werden.
Kommunikation in MPI:
Erklären Sie den Unterschied zwischen Blocking und Non-Blocking Kommunikation.
Beschreiben Sie, welche Funktion MPI Tags haben, um Nachrichten zu identifizieren.
Erklären Sie die allgemeinen Kommunikationsmechanismen in MPI.
Erläutern Sie, was MPI Window Objects sind und welche Rolle sie in der Kommunikation (z. B. in RMA, Remote Memory Access) spielen.
Solution (kurz & bündig)
Blocking vs. Non-Blocking:
Blocking: Funktionen wie MPI_Send/MPI_Recv blockieren bis zum Abschluss der Kommunikation.
Eröffnen einen freigegebenen Speicherbereich für One-Sided Communication (z. B. MPI_Put, MPI_Get).
Beispiel:
int *win_buffer;MPI_Win win;MPI_Alloc_mem(sizeof(int), MPI_INFO_NULL, &win_buffer);MPI_Win_create(win_buffer, sizeof(int), sizeof(int), MPI_INFO_NULL, MPI_COMM_WORLD, &win);MPI_Win_fence(0, win);if (rank == 0) { int value = 42; MPI_Put(&value, 1, MPI_INT, 1, 0, 1, MPI_INT, win);}MPI_Win_fence(0, win);MPI_Win_free(&win);MPI_Free_mem(win_buffer);
Dabei kann Prozess 0 direkt in den Speicher von Prozess 1 schreiben.
Aufgabe 9: HPC Netzwerk-Kommunikation
Netzwerktechnologien:
Nennen Sie die zwei wesentlichen Netzwerk-Kommunikationstechnologien, die in HPC eingesetzt werden.
Lösung
In HPC werden vor allem zwei wesentliche Netzwerkkommunikationstechnologien eingesetzt:
InfiniBand:
Bietet extrem hohe Bandbreite und sehr geringe Latenzzeiten.
Ideal für rechenintensive Anwendungen, bei denen schnelle Datenübertragung zwischen Knoten entscheidend ist.
Ethernet:
Weit verbreiteter Standard, der eine gute Leistung für viele HPC-Anwendungen liefert.
Häufig in Kombination mit InfiniBand oder in weniger latenzkritischen Umgebungen eingesetzt.
Latency in HPC Networks:
Definieren Sie den Begriff Latency:
Erklären Sie, in welchen Anwendungen die Latenz ein kritischer Leistungsindikator ist.
Lösung: Definition & Formel der Latency in HPC Networks
Definition:
Latency (Latenz) bezeichnet die feste Zeitverzögerung, die auftritt, bevor die tatsächliche Datenübertragung beginnt.
Es ist die Zeitspanne, die zwischen dem Start einer Kommunikationsoperation (z. B. dem Aufruf von MPI_Send) und dem Erreichen des ersten Übertragungspunkts (z. B. Empfang der ersten Datenbyte) vergeht.
Formel zur Berechnung der Übertragungszeit T(m):
T(m)=L+Bm
Erklärung:
L: Die Latenz, also die feste Verzögerungszeit, die vor der Datenübertragung anfällt.
m: Die Größe der Nachricht (z. B. in Byte).
B: Die Bandbreite des Netzwerks (z. B. in Byte/s), also die Datenmenge, die pro Zeiteinheit übertragen werden kann.
Diese Formel trennt die Gesamtdauer T(m) in einen latenzabhängigen Anteil L und einen übertragungsabhängigen Anteil Bm, der direkt mit der Nachrichtengröße zusammenhängt.
Praxisbeispiel:
Entwickeln Sie ein Programmkonzept, das die Inter-Node Latency misst.
Lösung: Programmkonzept zur Messung der Inter-Node Latency
Konzept:
Ziel: Messen Sie die Zeitverzögerung (Latency) zwischen zwei Knoten in einem HPC-Netzwerk.
Methode: Führen Sie einen Ping-Pong-Test mit zwei Prozessen (idealerweise auf verschiedenen Knoten) durch, um die Round-Trip Time (RTT) zu ermitteln.
Prozess 0 startet den Timer, sendet eine kleine Nachricht (z. B. ein einzelnes Integer) an Prozess 1.
Prozess 1 empfängt die Nachricht und sendet sie sofort zurück.
Prozess 0 stoppt den Timer nach dem Empfang der Rückmeldung.
Messung: Wiederholen Sie den Ping-Pong-Test in mehreren Iterationen (z. B. 1000-mal), um eine stabile Durchschnittsmessung zu erhalten.
MPI_Wtime(): Liefert die aktuelle Zeit in Sekunden und dient zur präzisen Zeitmessung.
Wiederholung: Mehrere Iterationen helfen, zufällige Messungenauigkeiten zu minimieren.
Berechnung: Die RTT wird durch 2 geteilt, um die Latenz des einfachen Nachrichtenversands (einseitig) zu bestimmen.
Fazit: Dieses Programmkonzept misst die Inter-Node Latency, indem es den klassischen Ping-Pong-Test in einer MPI-Umgebung verwendet, um so die Kommunikationsverzögerung zwischen zwei Knoten in einem HPC-Netzwerk zu quantifizieren.
Aufgabe 10: Historische Verbesserung der Latenz
Diskussion:
Diskutieren Sie die historischen Verbesserungen der Latenz in Bezug auf:
Computational to Bandwidth
Floating
Erklären Sie, wie sich diese Verbesserungen auf moderne HPC-Systeme ausgewirkt haben.
Erklärung: Computational to Bandwidth & Floating
Computational to Bandwidth:
Beschreibt das Verhältnis zwischen der Rechenleistung (FLOPS) und der verfügbaren Netzbandbreite.
Entscheidend für die Systembalance: Hohe Rechenleistung kann nur dann voll ausgenutzt werden, wenn die Datenübertragung schnell genug erfolgt.
Floating:
Bezieht sich auf die Fähigkeit eines Systems, Gleitkommaoperationen (Floating-Point Operations) effizient durchzuführen.
Ein zentraler Leistungsindikator in HPC, da viele wissenschaftliche und numerische Berechnungen auf Gleitkommaoperationen basieren.
Lösung: Historische Verbesserung der Latenz in HPC
Computational to Bandwidth:
Früher: Rechenleistung wuchs schneller als Bandbreite, was zu hohen Latenzen führte.
Heute: Systeme sind so ausbalanciert, dass latenzarme Netzwerke (z. B. InfiniBand) die zunehmende Rechenleistung unterstützen.
Floating:
Früher: Massive Steigerungen in der Floating-Point-Leistung wurden oft durch langsame Kommunikationswege ausgebremst.
Heute: Optimierte Netzwerkprotokolle und Interconnects reduzieren die Latenz, sodass hohe FP-Leistungen voll genutzt werden.
Gesamtauswirkung: Moderne HPC-Systeme kombinieren starke Rechenleistung, optimierte Bandbreite und extrem niedrige Latenzen, um maximale Performance zu erzielen.
Aufgabe 11: Surface-to-Volume Ratio in HPC Systems
Definition:
Erklären Sie das Surface-to-Volume Ratio, welches z. B. wie folgt definiert werden kann:
R=VolumeSurface Area
Bedeutung:
Diskutieren Sie, warum dieses Verhältnis ein wichtiger Leistungsindikator für die Performance von HPC-Systemen ist, insbesondere in Bezug auf Kommunikations- und Speicherzugriffsmuster.
Lösung: Surface-to-Volume Ratio in HPC Systems
Definition:
Das Surface-to-Volume Ratio RR wird definiert als
R=VolumeSurface Area
Es vergleicht die Größe der Oberfläche (etwa Kommunikationsgrenzen oder Speicherzugriffsflächen) mit der Größe des Volumens (etwa der rechenintensiven Datenmenge oder des Verarbeitungsraums).
Bedeutung:
Kommunikationsaufwand:
In HPC-Anwendungen sind häufig Daten auf dem Rand (Surface) eines Teilgebiets anfällig für Kommunikationsoperationen.
Ein hohes RR bedeutet, dass im Vergleich zum Berechnungsvolumen ein großer Anteil an Daten über Knoten hinweg ausgetauscht werden muss, was zu steigenden Kommunikationskosten führt.
Rechen- vs. Kommunikationsbalance:
Ein niedriges Surface-to-Volume Ratio zeigt, dass mehr Berechnungsarbeit innerhalb des Volumens erfolgt, während weniger Daten grenzüberschreitend kommuniziert werden müssen.
Dies verbessert typischerweise die Performance, da die Kosten für Datentransfer im Vergleich zur reinen Rechenarbeit minimiert werden.
Optimierung von Domänendecomposition:
Bei der Aufteilung von Aufgaben in parallele Teile (z. B. in Rasterberechnungen) wird ein geringeres RR angestrebt, um lokale Verarbeitung zu maximieren und Kommunikationsverzögerungen zu minimieren.
Fazit: Ein ausgewogenes Surface-to-Volume Ratio ist entscheidend, um die Kommunikations- und Speicherzugriffsmuster in HPC-Systemen effizient zu gestalten und die Gesamtperformance durch Minimierung der Kommunikationslatenz zu optimieren.
Definition:
Erklären Sie, was unter Graph Petitioning (im Kontext von Graph Partitioning) zu verstehen ist.
Bedeutung:
Diskutieren Sie, welche Rolle dieses Konzept bei der Optimierung von HPC-Anwendungen spielt, z. B. beim Ausbalancieren der Last in parallelen Berechnungen.
Aufgabe 13: Vektoraddition in UPC
Gegebene Situation:
Eine Vektoraddition wird durchgeführt, bei der gilt:
C=A+B
Das heißt, für jedes Element:
Ci=Ai+Bi
UPC “for all” Schleife:
Es gibt zwei Varianten der Schleife:
Eine Version verwendet den Ausdruck i als viertes Argument, die andere i + 1.
Ein typischer Loop könnte so aussehen:
for (i = 0; i < N; i++; <Vierte Ausdruck>) { C[i] = A[i] + B[i]; }
Aufgabenstellung:
Erklären Sie die Rolle des vierten Ausdrucks in der UPC “for all” Schleife.
Diskutieren Sie, welche Performance-Unterschiede sich aus der Verwendung von i gegenüber i + 1 als viertes Argument ergeben.
Erörtern Sie die Implikationen, die dieser vierte Parameter für die Iterationsaufteilung und Datenverteilung hat.
×
MyUniNotes is a free, non-profit project to make education accessible for everyone.
If it has helped you, consider giving back! Even a small donation makes a difference.
These are my personal notes. While I strive for accuracy, I’m still a student myself. Thanks for being part of this journey!