Python 3.15 JIT progress depends on more than code generation
The experimental JIT now reports preliminary gains on two platforms. Its design explains why better traces and reference handling matter alongside machine code generation.
Ken Jin's March 23 progress report says the Python 3.15 alpha JIT has reached its performance goals on macOS AArch64 and x86-64 Linux. The reported geometric-mean gains are about 11 to 12 percent against the tail-calling interpreter on macOS and 5 to 6 percent against the standard interpreter on Linux. These are preliminary results with different baselines. Individual benchmarks range from a slowdown of about 20 percent to speedups above 100 percent, excluding one microbenchmark.
I find the distribution more useful than a single headline percentage. My reason for choosing Python is usually its ecosystem, including libraries whose difficult work already exists in extensions. A faster interpreter is welcome, but I would want to know how much of a particular program executes in the code the JIT can improve. The reported mean does not answer that application-specific question.
The design background in PEP 744 helps explain the work. The proposal describes CPython's experimental copy-and-patch JIT and the tradeoffs behind it. It also makes clear why obtaining machine code is only part of obtaining a faster runtime.
The interpreter already supplies information
PEP 744 describes the specializing adaptive interpreter introduced in Python 3.11. That interpreter replaces bytecode instructions with specialized versions as code executes. It gathers information about the types and execution paths it encounters. The JIT can use that information when deciding what code to optimize.
The limitation is the size of the unit being optimized. Specializing one bytecode instruction leaves opportunities that require examining a sequence of operations. PEP 744 explains that CPython can translate instructions into smaller micro-operations, optimize sequences of those operations, and execute them with another interpreter. Those smaller operations permit more detailed optimization, but interpreting them also has a cost.
That creates a specific engineering problem. Splitting work into smaller operations can make transformations easier while increasing the amount of dispatch needed to execute the result. An optimization that removes some computation can lose its benefit if the execution mechanism introduces enough overhead. This is why an optimized sequence of micro-operations does not automatically imply a faster program.
The JIT compiles those optimized sequences to machine code. PEP 744 identifies several potential savings. Straight-line code removes dispatch between micro-operations. Embedding known values in instructions reduces repeated decoding. Moving data into hardware registers can reduce memory traffic. These are mechanisms to investigate, not a promise that every sequence achieves every saving.
Copy-and-patch divides build time from execution time
The copy-and-patch approach uses precompiled machine-code templates. PEP 744 explains that LLVM compiles the templates during the interpreter build. At runtime, CPython uses those templates to generate executable code without depending on LLVM as a runtime library.
This division matters for both deployment and maintenance. Adding a large optimizing compiler dependency to every Python process would carry costs beyond compilation speed. The chosen approach does substantial preparation while building the interpreter, then uses the generated material at execution time. A Python user does not need a runtime LLVM installation merely because the interpreter contains this JIT.
The templates also derive from the instruction definitions used elsewhere in the interpreter. PEP 744 presents that shared definition as a maintenance advantage. Changes to an instruction can update generated interpreter machinery and the JIT backend together. A project with many supported systems needs to account for who will maintain those implementations after the initial optimization work.
There are still tradeoffs. Generated machine code occupies memory in addition to the program's ordinary data. The proposal treats memory usage and build dependencies as part of the decision about enabling the JIT. I would measure those costs alongside execution time for an application whose deployment has a fixed memory limit.
Selecting useful code precedes compiling it
The March report attributes progress partly to a tracing frontend and partly to eliminating reference-count operations. It describes a dual-dispatch design that avoided duplicating the interpreter's instruction implementation. It also reports that tracing increased JIT code coverage by 50 percent. That coverage figure concerns the code available for JIT execution, not a 50 percent application speedup.
Jin's earlier November development plan explains the tension behind trace recording. The older selection method projected paths using interpreter cache information. Recording actual execution supplies current information about the path taken. Better selection can give later optimization passes more relevant work.
The same plan connects reference counting to register allocation. Decrementing a reference can invoke an arbitrary Python finalizer, so an apparently small operation can require the runtime to preserve state before control leaves the optimized sequence. Where another strong reference proves that an object remains alive, avoiding unnecessary reference operations can also reduce the need to move register contents back to memory.
That interaction is the part I would keep in mind when evaluating compiler work. Removing one operation can permit a later transformation whose benefit is larger. Conversely, generating better instructions for a poorly selected sequence leaves the selection problem unresolved. The pipeline has several places where a local improvement changes what the next stage can do.
Evaluate an experimental runtime as a configuration
PEP 744 separates meaningful performance improvement from the broader criteria for leaving experimental status. It also discusses distribution, deployment, and a Steering Council assessment of the tradeoffs. A measured speedup is evidence relevant to that decision. It does not establish every other condition by itself.
The proposal aims to preserve Python behavior and does not introduce a new package API for the JIT. That makes correctness comparison with the interpreter a useful starting point. I would run representative application tests under both configurations before considering the speed measurements. A benchmark result is unhelpful if the tested configuration changes an outcome the application depends on.
For performance evaluation, I would record the platform and interpreter baseline with each result. The March numbers use different baselines on the two reported systems, so treating them as a direct ranking between architectures would be unjustified. I would also retain results for individual workloads because the reported range includes regressions.
The current report says proper free-threading support is still absent and describes it as a target for subsequent work. An evaluation that requires that combination therefore has an unresolved prerequisite as of this report. For a compatible configuration, the next useful evidence is an application-level measurement with its memory cost and correctness results attached.