markiert einen wichtigen Punkt in der Entwicklung der weltweit beliebtesten Programmiersprache. Während Python seit langem für seine Lesbarkeit und sein großes Ökosystem bekannt ist, struggle seine Ausführungsgeschwindigkeit oft der „Elefant im Raum“.

Mit der Einführung von 3.14 hat das CPython-Kernentwicklungsteam nicht nur eine, sondern zwei der am meisten erwarteten Funktionen der letzten Zeit bereitgestellt.

Das Ende der GIL

Darüber habe ich schon einmal geschrieben. Echte Parallelität ist jetzt in Python verfügbar, wenn Sie es wünschen. Wenn Sie weitere Informationen zu GIL-freiem Python wünschen, hinterlasse ich am Ende einen Hyperlink zu meinem Artikel darüber.

Der Simply-In-Time (JIT)-Compiler

Diese experimentelle Funktion ist jetzt direkt in den offiziellen Installationsprogrammen enthalten und darauf konzentrieren wir uns hier. Es ist das Ergebnis jahrelanger Architekturvorbereitung durch das Python-Kernteam und andere mit dem Ziel, Python „standardmäßig schneller“ zu machen, ohne das C-Erweiterungs-Ökosystem zu beschädigen, das alles von der Datenwissenschaft bis zu Net-Backends antreibt.

In diesem Artikel werfen wir einen Blick auf das neue JIT, untersuchen, wie es sich von früheren Optimierungsbemühungen unterscheidet, und gehen einige Benchmarking-Methodologien durch, um Ihnen bei der Entscheidung zu helfen, ob es an der Zeit ist, das JIT für Ihre Workloads auszuprobieren.

Was ist Pythons neuer Simply-In-Time (JIT)-Compiler?

Um das 3.14 JIT zu verstehen, müssen wir wissen, wie Python traditionell ausgeführt wird. Commonplace-Python (CPython) ist ein interpretiert Sprache. Wenn Sie ein Skript ausführen, wird Ihr Code kompiliert Bytecode, Hierbei handelt es sich um eine Reihe von Anweisungen, die die virtuelle CPython-Maschine ausführt.

Das JIT ändert diesen Ablauf. Anstatt den Bytecode einfach Zeile für Zeile zu interpretieren, überwacht die JIT, welche Teile Ihres Codes am häufigsten ausgeführt werden (die „heißen“ Pfade). Wenn eine Funktion oder Schleife als „heiß“ eingestuft wird, übersetzt die JIT den Bytecode in nativen Maschinencode (Anweisungen, die die CPU versteht). Beim nächsten Aufruf des Codes ist dann keine Interpretation erforderlich. Stattdessen läuft es einfach so, wie es ist. Das kann eine große Zeitersparnis sein, wie wir später sehen werden.

Wie die JIT in CPython passt

Das Python 3.14 JIT ist keine völlige Neufassung. Es ist als Decide-in-Komponente konzipiert, die neben dem vorhandenen Interpreter arbeitet. Es verwendet eine Technik namens „Copy-and-Patch“, die es ermöglicht, dass JIT leichtgewichtig und über verschiedene CPU-Architekturen hinweg portierbar ist, ohne dass ein riesiges, komplexes Compiler-Backend wie LLVM erforderlich ist.

Was hat sich in Python 3.14 geändert?

Python 3.13 verfügte über ein grundlegendes, experimentelles JIT, das jedoch standardmäßig deaktiviert struggle. Wenn Sie es testen wollten, mussten Sie den CPython-Quellbaum klonen und ihn mit bestimmten experimentellen Flags kompilieren, z - - enable-experimental-jit.

Mit Python 3.14 hat sich alles geändert. Das JIT wurde in den offiziellen Installationsprogrammen .msi (Home windows) und .pkg (macOS) angeboten. Dies bedeutete auch, dass Sie keinen C-Compiler mehr auf Ihrem Laptop benötigten, um die JIT-Vorteile nutzen zu können. Obwohl es noch „experimentell“ ist, signalisiert die Aufnahme in die offiziellen Binärdateien, dass das Kernteam glaubt, dass das JIT stabil genug für breite Group-Checks ist.

Holen Sie sich Python 3.14

Gehen Sie rüber zu https://www.python.org/downloads/und Sie sehen eine Obtain-Possibility für 3.14. Klicken Sie darauf und folgen Sie dann den Anweisungen.

Alternativ, wenn Sie das haben UV Wenn das Software installiert ist, können Sie Folgendes eingeben.

PS C: > uv python set up 3.14

Aktivieren des JIT

Standardmäßig ist die JIT deaktiviert. Dies ist eine Sicherheitsmaßnahme; Da es experimentell ist, möchte der Python Steering Council sicherstellen, dass Benutzer nicht mit unerwarteten Rückschritten in der Stabilität oder Speichernutzung konfrontiert werden, ohne sich ausdrücklich dafür zu entscheiden.

Um die JIT zu aktivieren, verwenden Sie eine Umgebungsvariable. Dadurch wird die CPython-Laufzeitumgebung angewiesen, die JIT-Engine beim Begin zu initialisieren.

Unter Home windows (PowerShell):

$env:PYTHON_JIT=1
python my_script.py

Unter macOS/Linux (Bash/Zsh):

PYTHON_JIT=1
python my_script.py

Sobald CPython aktiviert ist, kompiliert CPython nicht sofort alles per JIT. Es verwendet a Stufensystem. Grundsätzlich wird versucht, den Code zunächst so kostengünstig wie möglich auszuführen, und der Kompilierungs-/Optimierungsaufwand wird nur für die Teile aufgewendet, die sich als heiß erweisen.

  • Stufe 0: Standardinterpretation.
  • Stufe 1: Spezialisierter Bytecode (eingeführt in 3.11).
  • Stufe 2 (JIT): Maschinencode-Generierung für die am häufigsten verwendeten Pfade.

Messung der Auswirkungen des JIT

Beim Testen eines JIT können Sie nicht einfach das verwenden time.time() um eine Funktion. JITs erfordern a Aufwärmphase. Die ersten paar Iterationen einer Schleife können langsamer als regular sein, da die JIT den Code profiliert, nachfolgende Iterationen können jedoch deutlich schneller sein.

Die Benchmark-Suite

Nachfolgend finden Sie eine umfassende Testsuite, die darauf ausgelegt ist, verschiedene Aspekte des JIT zu testen, von schwerer Mathematik bis hin zur komplexen Objektmanipulation.

Datei 1: workloads.py

Diese Datei enthält drei verschiedene CPU-gebundene Aufgaben.

1/ Die Mandelbrot-Funktion iteriert die Mandelbrot-Formel über ein Pixelraster und gibt eine Prüfsumme der Iterationszahlen professional Pixel zurück.

2/ Die Djikstra-Funktion erstellt einen deterministischen, zufallsgewichteten Graphen und führt Dijkstra vom Knoten 0 aus aus und gibt zurück, wie viele Knoten abgeschlossen/besucht wurden.

3/ Die Levenshtein-Funktion generiert N deterministische Zufallszeichenfolgenpaare und gibt die Summe ihrer Levenshtein-Abstände zurück

from __future__ import annotations

import random
import heapq

# Workload 1: Mandelbrot (CPU + math loops)
def mandelbrot(width: int = 1000, peak: int = 1000, iters: int = 500) -> int:
    checksum = 0
    for y in vary(peak):
        cy = (y / peak) * 2.4 - 1.2
        for x in vary(width):
            cx = (x / width) * 3.2 - 2.2
            zx, zy, depend = 0.0, 0.0, 0
            whereas zx * zx + zy * zy <= 4.0 and depend < iters:
                zx, zy = zx * zx - zy * zy + cx, 2.0 * zx * zy + cy
                depend += 1
            checksum += depend
    return checksum

# Workload 2: Dijkstra (heap + listing + logic)
def dijkstra(n: int = 10000, edges_per_node: int = 50, seed: int = 123) -> int:
    rng = random.Random(seed)
    graph = (() for _ in vary(n))
    for u in vary(n):
        for _ in vary(edges_per_node):
            v = rng.randrange(n)
            if v != u:
                graph(u).append((v, rng.randrange(1, 30)))

    dist = (10**12) * n
    dist(0) = 0
    pq = ((0, 0))
    visited = 0

    whereas pq:
        d, u = heapq.heappop(pq)
        if d != dist(u):
            proceed
        visited += 1
        for v, w in graph(u):
            nd = d + w
            if nd < dist(v):
                dist(v) = nd
                heapq.heappush(pq, (nd, v))

    return visited

# Workload 3: Levenshtein distance (dynamic programming)
def levenshtein(a: str, b: str) -> int:
    prev = listing(vary(len(b) + 1))
    for i, ca in enumerate(a, 1):
        cur = (i)
        for j, cb in enumerate(b, 1):
            cur.append(min(cur(j - 1) + 1, prev(j) + 1, prev(j - 1) + (ca != cb)))
        prev = cur
    return prev(-1)

def levenshtein_batch(n: int = 10000, seed: int = 7, ok: int = 50) -> int:
    """
    Deterministic batch: mounted RNG seed, mounted alphabet, mounted string size.
    Returns the sum of distances.
    """
    rng = random.Random(seed)
    alphabet = "abc"
    complete = 0
    for _ in vary(n):
        a = "".be part of(rng.decisions(alphabet, ok=ok))
        b = "".be part of(rng.decisions(alphabet, ok=ok))
        complete += levenshtein(a, b)
    return complete

Datei 2: benchmark.py

Dieses Skript automatisiert den Vergleich verschiedener Workloads mit aktiviertem und deaktiviertem JIT.

import os
import time
import json
import subprocess
from pathlib import Path

PYTHON_EXE = r"C:UsersthomaAppDataLocalProgramsPythonPython314python.exe"
PROJECT_DIR = Path(__file__).resolve().mother or father

# Authentic workloads (assertion prints a consequence for sanity)
WORKLOADS = (
    ("mandelbrot", 'from workloads import mandelbrot; print(mandelbrot())'),
    ("dijkstra", 'from workloads import dijkstra; print(dijkstra())'),
    ("levenshtein_batch", 'from workloads import levenshtein_batch; print(levenshtein_batch())'),
)

N_RUNS = 10  # common of ALL runs (set to six/10/20 as you want)
OUTFILE = PROJECT_DIR / "results_avg.json"

def run_once(stmt: str, jit_val: int) -> tuple(float, str):
    env = os.environ.copy()
    env("PYTHON_JIT") = str(jit_val)

    # Guarantee native workloads.py is importable in subprocess
    env("PYTHONPATH") = str(PROJECT_DIR) + (os.pathsep + env.get("PYTHONPATH", ""))

    t0 = time.perf_counter()
    p = subprocess.run(
        (PYTHON_EXE, "-c", stmt),
        env=env,
        cwd=str(PROJECT_DIR),
        capture_output=True,
        textual content=True,
    )
    t1 = time.perf_counter()

    if p.returncode != 0:
        elevate RuntimeError(
            f"Run failed (PYTHON_JIT={jit_val})nn"
            f"Assertion:n{stmt}nn"
            f"STDOUT:n{p.stdout}nnSTDERR:n{p.stderr}"
        )

    return (t1 - t0, p.stdout.strip())

def summarize(instances: listing(float)) -> dict:
    return {
        "avg": sum(instances) / len(instances),
        "min": min(instances),
        "max": max(instances),
        "runs": instances,
    }

def bench_workload(identify: str, stmt: str) -> dict:
    outcomes = {}
    outputs = {}

    for jit_val in (0, 1):
        instances = ()
        outs = ()
        print(f"  PYTHON_JIT={jit_val}: working {N_RUNS} instances...")
        for i in vary(1, N_RUNS + 1):
            dt, out = run_once(stmt, jit_val)
            instances.append(dt)
            outs.append(out)
            print(f"    run {i}/{N_RUNS}: {dt:.6f}s")

        outcomes(jit_val) = summarize(instances)
        outputs(jit_val) = outs

    avg0 = outcomes(0)("avg")
    avg1 = outcomes(1)("avg")
    speedup = avg0 / avg1 if avg1 else float("inf")
    delta_pct = (avg1 - avg0) / avg0 * 100.0 if avg0 else 0.0

    return {
        "workload": identify,
        "jit0": outcomes(0),
        "jit1": outcomes(1),
        "speedup_jit0_over_jit1": speedup,
        "delta_pct_jit1_vs_jit0": delta_pct,
        "outputs": outputs,  # sanity: must be steady
    }

def principal() -> int:
    all_results = ()
    print(f"Utilizing Python: {PYTHON_EXE}")
    print(f"Challenge dir: {PROJECT_DIR}")
    print(f"Runs per setting (avg of all runs): {N_RUNS}n")

    for identify, stmt in WORKLOADS:
        print(f"=== {identify} ===")
        r = bench_workload(identify, stmt)
        all_results.append(r)

        print(f"n  Averages:")
        print(f"    JIT=0 avg: {r('jit0')('avg'):.6f}s (min {r('jit0')('min'):.6f}, max {r('jit0')('max'):.6f})")
        print(f"    JIT=1 avg: {r('jit1')('avg'):.6f}s (min {r('jit1')('min'):.6f}, max {r('jit1')('max'):.6f})")
        print(f"    Speedup (JIT=0 / JIT=1): {r('speedup_jit0_over_jit1'):.3f}×  (Δ={r('delta_pct_jit1_vs_jit0'):+.2f}%)n")

        # Elective: warn if outputs differ throughout runs (nondeterminism)
        if len(set(r("outputs")(0))) != 1:
            print("  !! WARNING: JIT=0 output differs throughout runs (nondeterministic workload?)")
        if len(set(r("outputs")(1))) != 1:
            print("  !! WARNING: JIT=1 output differs throughout runs (nondeterministic workload?)")

    OUTFILE.write_text(json.dumps(all_results, indent=2), encoding="utf-8")
    print(f"Wrote: {OUTFILE}")
    return 0

if __name__ == "__main__":
    elevate SystemExit(principal())

Hier sind meine Ergebnisse.

C:Usersthomaprojectspython_jit>C:UsersthomaAppDataLocalProgramsPythonPython314python.exe benchmark.py
Utilizing Python: C:UsersthomaAppDataLocalProgramsPythonPython314python.exe
Challenge dir: C:Usersthomaprojectspython_jit
Runs per setting (avg of all runs): 10

=== mandelbrot ===
  PYTHON_JIT=0: working 10 instances...
    run 1/10: 6.890924s
    run 2/10: 6.950737s
    run 3/10: 7.265357s
    run 4/10: 6.947150s
    run 5/10: 6.932333s
    run 6/10: 6.939378s
    run 7/10: 7.194705s
    run 8/10: 6.995550s
    run 9/10: 6.902696s
    run 10/10: 7.256164s
  PYTHON_JIT=1: working 10 instances...
    run 1/10: 5.216740s
    run 2/10: 5.241888s
    run 3/10: 5.350822s
    run 4/10: 5.246767s
    run 5/10: 5.294771s
    run 6/10: 5.273295s
    run 7/10: 5.272135s
    run 8/10: 5.617062s
    run 9/10: 5.251656s
    run 10/10: 5.239060s

  Averages:
    JIT=0 avg: 7.027499s (min 6.890924, max 7.265357)
    JIT=1 avg: 5.300420s (min 5.216740, max 5.617062)
    Speedup (JIT=0 / JIT=1): 1.326×  (Δ=-24.58%)

=== dijkstra ===
  PYTHON_JIT=0: working 10 instances...
    run 1/10: 0.235401s
    run 2/10: 0.227603s
    run 3/10: 0.244492s
    run 4/10: 0.232971s
    run 5/10: 0.249589s
    run 6/10: 0.232229s
    run 7/10: 0.229422s
    run 8/10: 0.238399s
    run 9/10: 0.230657s
    run 10/10: 0.235772s
  PYTHON_JIT=1: working 10 instances...
    run 1/10: 0.238862s
    run 2/10: 0.239266s
    run 3/10: 0.240312s
    run 4/10: 0.231413s
    run 5/10: 0.232692s
    run 6/10: 0.233783s
    run 7/10: 0.230016s
    run 8/10: 0.237760s
    run 9/10: 0.240895s
    run 10/10: 0.246033s

  Averages:
    JIT=0 avg: 0.235653s (min 0.227603, max 0.249589)
    JIT=1 avg: 0.237103s (min 0.230016, max 0.246033)
    Speedup (JIT=0 / JIT=1): 0.994×  (Δ=+0.62%)

=== levenshtein_batch ===
  PYTHON_JIT=0: working 10 instances...
    run 1/10: 2.176256s
    run 2/10: 2.171253s
    run 3/10: 2.171834s
    run 4/10: 2.170444s
    run 5/10: 2.149874s
    run 6/10: 2.162820s
    run 7/10: 2.171975s
    run 8/10: 2.199151s
    run 9/10: 2.168398s
    run 10/10: 2.167821s
  PYTHON_JIT=1: working 10 instances...
    run 1/10: 1.575666s
    run 2/10: 1.612615s
    run 3/10: 1.571106s
    run 4/10: 1.584650s
    run 5/10: 1.579948s
    run 6/10: 1.582633s
    run 7/10: 1.593924s
    run 8/10: 1.573608s
    run 9/10: 1.581427s
    run 10/10: 1.578553s

  Averages:
    JIT=0 avg: 2.170983s (min 2.149874, max 2.199151)
    JIT=1 avg: 1.583413s (min 1.571106, max 1.612615)
    Speedup (JIT=0 / JIT=1): 1.371×  (Δ=-27.06%)

Interpretation der Ergebnisse

Wie Sie sehen, sind die Ergebnisse gemischt. Dies ist für ein experimentelles JIT regular.

  • 10–30 % Beschleunigung: Häufig in „reinen Python“-Schleifen (wie den Mandelbrot- oder Levenshtein-Checks), bei denen die JIT den Overhead der Bytecode-Dispatch-Schleife vermeiden kann.
  • 0 % Verbesserung: Häufig bei E/A-gebundenen Aufgaben oder Code, der häufig C-Erweiterungen verwendet. Der Dijkstra-Code wurde nicht schneller, da seine Laufzeit von Heap-/Tupeloperationen und speicherintensiver, zuordnungsgesteuerter Arbeit dominiert wird, die das aktuelle CPython-JIT nicht wesentlich optimiert, sodass etwaige Einsparungen durch den Interpreter im Lärm verloren gehen.

Wann sollte Python 3.14 JIT verwendet werden?

Das JIT ist ein leistungsstarkes Software, aber kein „Zauberknopf“. Meiner Erfahrung nach sollten Sie das JIT ausprobieren, wenn Sie …

  • CPU-gebundene Logik: Ihre Anwendung führt umfangreiche Berechnungen, Datenverarbeitung oder komplexe Logik in reinem Python durch.
  • Lang laufende Prozesse: Webserver (Gunicorn/Uvicorn) oder Hintergrundarbeiter (Celery), die stundenlang laufen, geben dem JIT genügend Zeit, sich aufzuwärmen und Scorching Paths zu optimieren.
  • Experimentelle Checks: Sie möchten Ihre Codebasis für zukünftige Versionen von Python (3.15+) vorbereiten, bei denen die JIT wahrscheinlich aggressiver sein wird.

Und vermeiden Sie es, wenn Sie …

  • E/A-gebundene Apps: Wenn Ihre App nur auf Datenbankabfragen oder API-Antworten wartet, hilft die JIT nicht.
  • Umgebungen mit eingeschränktem Speicher: Kleine Lambda-Funktionen oder winzige Container können unter dem erhöhten Speicherbedarf des JIT-Cache leiden.
  • Kurzlebige CLI-Instruments: Ein Skript, das in weniger als einer Sekunde ausgeführt wird, benötigt kein JIT.

Zukünftige Richtungen: Über 3.14 hinaus

Das CPython-Kernteam betrachtet 3.14 als „Gründungsjahr“. Zukünftige Iterationen (Python 3.15 und 3.16) werden voraussichtlich Folgendes umfassen:

  • Tiefergehende Optimierungsdurchgänge: Nutzung der zur Laufzeit gesammelten Typinformationen, um eine noch aggressivere Maschinencodegenerierung durchzuführen.
  • Bessere Heuristik: Intelligentere Entscheidungen über Wann zu kompilieren, wodurch die „Aufwärm“-Strafe reduziert wird.
  • Geringerer Overhead: Verfeinerung des Kopier- und Patchmechanismus zur Reduzierung des Speicherverbrauchs.

Zusammenfassung

Der JIT von Python 3.14 ist mehr als nur ein Leistungspatch. Es ist eine Absichtserklärung. Es zeigt, dass Python es ernst meint, die Leistungslücke zu Sprachen wie Java oder Go zu schließen und gleichzeitig die „Batterien enthalten“-Einfachheit beizubehalten, die es berühmt gemacht hat.

Für die meisten Entwickler ist JIT einfach ein weiteres Software, das es wert ist, im Auge behalten zu werden. Wenn die Leistung in Ihren Projekten wichtig ist, lohnt es sich, Python 3.14 anhand Ihrer vorhandenen Arbeitslasten zu testen. Ein paar Benchmarks Ihrer wichtigsten Codepfade könnten Leistungssteigerungen aufdecken, wo Sie sie nicht erwartet haben.

Hier ist der Hyperlink zu meinem vorherigen Artikel über GIL Payment Python, den ich zu Beginn erwähnt habe.


Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert