Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Parallele Ausführung

PetaPerl parallelisiert geeignete Schleifen automatisch mit Rayon, einem Work-Stealing-Thread-Pool. In Kombination mit JIT-Kompilierung ermöglicht dies dramatische Beschleunigungen für rechenintensive Aufgaben.

Funktionsweise

Wenn PetaPerl eine parallelisierbare Schleife erkennt, geschieht Folgendes:

  1. Analyse des Schleifenrumpfes auf Seiteneffekte und gemeinsam veränderlichen Zustand
  2. Identifikation von Reduktionsvariablen (Akkumulatoren wie $sum += ...)
  3. Verteilung der Iterationen über Threads mittels Rayons Work-Stealing-Scheduler
  4. Zusammenführung der Ergebnisse unter Verwendung der erkannten Reduktionsoperationen

Jeder Thread erhält seine eigene Kopie der schleifen-lokalen Variablen. Reduktionsvariablen werden nach Abschluss aller Threads zusammengeführt.

Was parallelisiert wird

JIT-kompilierte While-Schleifen

Wenn der JIT eine While-Schleife kompiliert und die Analyse Folgendes erkennt:

  • Eine Zählvariable mit bekannten Grenzen
  • Reduktionsvariablen (reines Akkumulationsmuster)
  • Keine I/O-Operationen oder Seiteneffekte im Schleifenrumpf

Dann wird der Schleifenrumpf einmal kompiliert und parallel über mehrere Threads ausgeführt.

Eingebaute Funktionen

map und grep mit reinen Callbacks können parallel ausgeführt werden:

my @results = map { expensive_computation($_) } @large_array;
my @filtered = grep { complex_test($_) } @large_array;

Voraussetzungen für Parallelisierung:

  • Keine Seiteneffekte im Callback
  • Kein gemeinsam veränderlicher Zustand
  • Sammlungsgröße über dem Parallelisierungs-Schwellenwert

CLI-Steuerung

# Standard: Parallelisierung aktiviert
pperl script.pl

# Parallelisierung deaktivieren
pperl --no-parallel script.pl

# Explizit aktivieren (Standard)
pperl --parallel script.pl

# Thread-Anzahl festlegen (Standard: Anzahl der CPU-Kerne)
pperl --threads=4 script.pl

# Minimale Sammlungsgröße für Parallelisierung festlegen
pperl --parallel-threshold=1000 script.pl

Der Testrahmen läuft standardmäßig mit --no-parallel, um deterministische Testausgaben sicherzustellen.

Leistung

Mandelbrot-Menge (1000x1000)

ModusZeitvs. perl5
perl512.514ms1,0x
pperl Interpreter~3.500ms3,6x schneller
pperl JIT163ms76x schneller
pperl JIT + parallel (8 Threads)29ms431x schneller

Skalierung

Der Work-Stealing-Scheduler liefert nahezu lineare Skalierung für trivial parallelisierbare Aufgaben:

ThreadsMandelbrot 4000x4000Skalierung
1Basislinie1,0x
2~50% Zeit~1,9x
4~25% Zeit~3,8x
8~13% Zeit~5,2x

Die Skalierung ist sublinear aufgrund von Speicherbandbreite, Cache-Effekten und Reduktions-Overhead.

Einschränkungen

String-Operationen

String-Operationen (.= Concat, String-Aufbau) werden nicht parallelisiert. Die JIT-String-Unterstützung verwendet Extern Calls zurück in die Rust-Laufzeitumgebung, die veränderlichen Zugriff auf gemeinsamen Zustand erfordert. Wenn der JIT String-Variablen in einer Schleife erkennt, wird die parallele Ausführung deaktiviert.

Seiteneffekt-Erkennung

Der Parallelisierungs-Analysator ist konservativ. Jeder der folgenden Punkte schließt eine Schleife aus:

  • I/O-Operationen (print, open, Datei-Lesezugriffe)
  • Schreiben globaler Variablen
  • Unterprogrammaufrufe (sofern nicht als rein nachgewiesen)
  • Regex-Operationen mit Seiteneffekten (s///)

Falsch-Negative (verpasste Parallelisierungsmöglichkeiten) sind sicher — die Schleife wird einfach sequenziell ausgeführt. Falsch-Positive (fehlerhafte Parallelisierung) wären Fehler.

Determinismus

Parallele Ausführung kann die Reihenfolge von Seiteneffekten verändern. Aus diesem Grund wird Parallelisierung nur angewendet, wenn die Analyse beweist, dass der Schleifenrumpf frei von beobachtbaren Seiteneffekten ist.

Die Ausgabereihenfolge wird bei map und grep beibehalten — das Ergebnis-Array behält dieselbe Elementreihenfolge wie bei sequenzieller Ausführung.

Wie die Reduktionserkennung funktioniert

Der Analysator identifiziert Reduktionsvariablen, indem er nach Akkumulationsmustern sucht und Rücksetzmuster davon abzieht:

# Als Reduktion erkannt: $sum akkumuliert, wird nie in der Schleife zurückgesetzt
my $sum = 0;
for my $x (@data) {
    $sum += $x;
}

# KEINE Reduktion: $temp wird bei jeder Iteration zurückgesetzt
for my $x (@data) {
    my $temp = $x * 2;  # Rücksetzung (my-Deklaration)
    $sum += $temp;       # $sum ist weiterhin eine Reduktion
}

Die Formel: Reduktionen = Akkumulationen - Rücksetzungen. Dies verhindert Falsch-Positive, bei denen eine Variable innerhalb des Schleifenrumpfes sowohl akkumuliert als auch zurückgesetzt wird.