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

JIT-Kompilierung

PetaPerl enthält einen JIT-Compiler (Just-In-Time), der auf Cranelift basiert — demselben Backend, das auch von Wasmtime verwendet wird. Der JIT kompiliert häufig ausgeführte Schleifen in nativen Maschinencode und liefert dabei Leistung, die perl5 um Größenordnungen übertreffen kann.

Was JIT-kompiliert wird

Der JIT zielt auf Schleifen ab — for und while — die Arithmetik- und Vergleichsoperationen enthalten. Im Einzelnen:

Ganzzahlige For-Schleifen

my $sum = 0;
for my $i (1..1_000_000) {
    $sum += $i;
}

Der JIT erkennt das Ganzzahl-Akkumulator-Muster und kompiliert den Schleifenrumpf in nativen Code. Die Schleifenvariable ($i) und Akkumulatoren ($sum) werden in CPU-Registern gehalten.

While-Schleifen mit Gleitkomma-Arithmetik

while ($y < $max_y) {
    my $cx = $x * $scale;
    my $cy = $y * $scale;
    # ... Berechnung ...
    $y += $step;
}

Gleitkomma-Arithmetik (+, -, *, /), Vergleiche (<, >, <=, >=) und Kontrollfluss (if/last) innerhalb von While-Schleifen werden in native SSE/AVX-Instruktionen kompiliert.

Verschachtelte Schleifen

Der JIT verarbeitet verschachtelte Schleifen bis zu 5 Ebenen tief. Jede Verschachtelungsebene erzeugt ihren eigenen Satz von Cranelift-Basisblöcken mit korrekten Phi-Knoten-Verbindungen für Variablen, die zwischen den Ebenen fließen.

String-Operationen (über Extern Calls)

Der JIT unterstützt .= (Concat-Assign) und $x = "" (Leeren) auf String-Variablen durch externe Funktionsaufrufe aus JIT-kompiliertem Code zurück in die Rust-Laufzeitumgebung.

Was nicht JIT-kompiliert wird

  • Unterprogrammaufrufe — der Overhead von Funktionsaufrufen dominiert, der JIT-Vorteil ist marginal
  • Regex-Operationen — die Regex-Engine hat ihren eigenen Optimierungspfad
  • I/O-Operationen — I/O-gebundener Code profitiert nicht vom JIT
  • Komplexer Datenstrukturzugriff — Hash-/Array-Operationen mit dynamischen Schlüsseln
  • String-intensive Berechnungen — String-Aufbau wird stattdessen durch die PvBuf-Optimierung behandelt

Code, der nicht JIT-kompiliert wird, läuft weiterhin auf dem Interpreter, der seine eigenen Schnellpfade für häufige Operationen besitzt.

Leistung

Ackermann-Funktion

Der Interpreter-Schnellpfad (nicht JIT) verarbeitet rekursive Ganzzahlarithmetik:

LaufzeitumgebungZeitBeschleunigung
perl5630ms1,0x
pperl (Interpreter)14ms45x schneller

Mandelbrot-Menge (1000x1000)

JIT-Kompilierung verschachtelter While-Schleifen mit Gleitkomma-Arithmetik:

LaufzeitumgebungZeitBeschleunigung
perl512.514ms1,0x
pperl (nur JIT)163ms76x schneller
pperl (JIT + parallel)29ms431x schneller

Wie das erreicht wird

  1. Registerallokation: Schleifenvariablen in CPU-Registern statt auf dem Interpreter-Stack
  2. Typspezialisierung: Variablen, die nachweislich Ganzzahl oder Gleitkomma sind, verwenden direkt native Instruktionen
  3. Branch-Eliminierung: Konstante Bedingungen werden zur Kompilierzeit entfernt
  4. Kein Dispatch-Overhead: Nativer Code ersetzt die Interpreter-Dispatch-Schleife innerhalb JIT-kompilierter Bereiche vollständig

CLI-Steuerung

# Standard: JIT aktiviert
pperl script.pl

# JIT deaktivieren (nur Interpreter)
pperl --no-jit script.pl

Der Testrahmen läuft standardmäßig mit --no-jit, um die Korrektheit des Interpreters zu testen. JIT-spezifische Tests in t/62-jit/ überschreiben dies.

Architektur

Kompilierungspipeline

Schleife erkannt → Variablen und Typen analysieren → JIT-IR aufbauen
  → Über Cranelift kompilieren → Kompilierte Funktion cachen → Nativen Code ausführen

Caching

Kompilierte Funktionen werden anhand der enterloop-Op-ID in der Op-Arena gecacht. Ein CachedWhileLoop speichert:

  • Den kompilierten nativen Funktionszeiger
  • Variablen-Typinformationen (Gleitkomma vs. Ganzzahl vs. String)
  • Einen konstanten String-Pool (für String-Operationen)
  • Metadaten zur Eignung für parallele Ausführung

Nachfolgende Iterationen derselben Schleife verwenden die gecachte Kompilierung wieder.

JIT-IR

Der JIT verwendet eine eigene Zwischendarstellung (JitIr), die Perl-Operationen auf Cranelift-Operationen abbildet:

  • JitIr::WhileLoop { condition_ir, body_ir } — rekursiv für Verschachtelung
  • JitIr::FloatVar / JitIr::IntVar — typisierter Variablenzugriff
  • JitIr::BinOp — Arithmetik und Vergleiche
  • JitIr::ExitIfFalse / JitIr::ExitIfTrue — Schleifenaustritt und last

Variablentypen

Der JIT verfolgt zwei Variablentypen:

  • JitType::F64 — Gleitkommawerte in einem f64-Puffer
  • JitType::Ptr — String-Werte, die über Extern Calls an die Rust-Laufzeitumgebung zugegriffen werden

Variablen werden anhand ihres Verwendungsmusters im Schleifenrumpf typisiert. Variablen mit gemischten Typen fallen auf den Interpreter zurück.