💰 Calculate Gross-Net Salary→

CPython's Copy-and-Patch JIT After PEP 836: What Was Measured and Why It Was Withdrawn

· Aloka Technology· 0 views· 6 min read
CPython's Copy-and-Patch JIT After PEP 836: What Was Measured and Why It Was Withdrawn

PEP 836, the plan to make CPython's experimental JIT a supported feature, was withdrawn on October 19, 2026. This article covers how the JIT works, what its authors measured, and how to test it yourself.

A 4 to 12 percent geometric-mean speedup on pyperformance is real, but it is not the kind of number that justifies a permanent compiler in an interpreter's main branch. That tension sits behind a decision made this month: on October 19, 2026, the authors of PEP 836 withdrew their proposal to turn CPython's experimental JIT into a supported feature.

The question: what does the JIT actually do, what was measured, and what should a team running Python in production conclude?

Background: the pause and the withdrawal

The JIT has lived in CPython's main branch since 3.13 as an experimental, opt-in feature. According to LWN's reporting, in June 2026 the Steering Council paused new JIT development in main, except bug and security fixes, and gave a six-month window to submit and resolve a PEP, after which the code would be removed if none was accepted. It asked about long-term maintenance, compatibility with other features and tooling, architectural stability, and measurable success metrics. Ken Jin announced the draft PEP 836 on July 3, and the Council held office hours on August 6.

The PEP page now shows status Withdrawn, with a resolution dated October 19, 2026. InfoWorld, citing LWN, quotes Savannah Ostrowski: "we don't feel we have the personal bandwidth to carry the current version forward." InfoWorld reports the Council found the PEP lacked detail on long-term support, performance evaluation and integration with debuggers and profilers, and considered the expected gains overstated. I could not read the linked Discourse resolution post, so those reasons are secondary reporting. Secondary sources also say the JIT will be removed from main early in the 3.16 cycle; I did not find the Council's own formal announcement, so treat the timing as reported, not confirmed.

How it works: three stages

PEP 836 describes the current design in three parts.

  • Frontend (tracing). It records the actual straight-line path through bytecode, with live values, and turns it into micro-operations (uops). The PEP proposed replacing this with a method-based frontend by 3.16, with a possible rollback.
  • Middle end. An optimizer works on uops using type information. It is written as a bytecode DSL over the interpreter, so it reasons about types instead of objects. Listed optimizations are type speculation, guard removal, redundant reference-count removal and constant folding.
  • Backend (copy and patch). Machine code is assembled from precompiled templates called stencils, built from C templates at build time, rather than written by hand per architecture. LLVM is required at build time to generate the stencils; the PEP left that unchanged.

Deep analysis: what the numbers say

The PEP's abstract claims about 4 to 12 percent geometric-mean improvement on pyperformance across measured Tier 1 platforms for Python 3.15. Its appendix lists per-machine averages for runs between June 16 and June 27, 2026:

MachineConfigurationSpeedup vs interpreter
M3 Pro, macOSJIT + TAILCALL1.126x
AmpereOne, Linux aarch64JIT1.073x
i5-8400, Linux x86_64JIT1.069x
Ryzen 5 3600X, WindowsJIT + TAILCALL1.047x

These are the authors' figures, from the PEP, and I did not reproduce them. Note the spread: the best result includes the tail-calling interpreter option, so part of it is not attributable to the JIT alone, though the PEP page I read does not break that out. A prototype method-based JIT was reported 5 to 6 percent faster than JIT-off on a GIL build on x86-64 Linux, against 7 to 8 percent for the tracing JIT in the same configuration, and was incomplete (no generator or coroutine support, no polymorphism).

The proposed milestones show why the bar was high. Year one (3.16 beta 1): method frontend, free-threading compatibility, at least 5 percent uplift for JIT versus the GIL interpreter. Year two (3.17 beta 1): at least 20 percent geometric-mean improvement on pyperformance for JIT plus free-threading over the free-threaded interpreter alone. The PEP did not define explicit removal criteria, only a re-evaluation if goals were missed.

Comparison with alternatives

OptionStrengthCost
CPython adaptive interpreter (3.11+)Type-specialized bytecode, no JIT buildGains limited to what specialization gives
CPython experimental JITSame runtime and C-extension ABI; single-digit to low-teens percent on pyperformance (PEP figures)Disabled in official binaries by default; LLVM at build time; slated for removal from main
PyPyInfoWorld describes it as potentially an order of magnitude fasterLags CPython and has C-extension compatibility issues
CinderX (Meta)JIT add-onDescribed as experimental

Practical example: measuring it on your workload

Per the PEP, official Windows and macOS binaries ship with the JIT built but disabled, and PYTHON_JIT=1 enables it. The following harness is an untested sketch; it assumes a JIT-enabled CPython 3.15 build, the pyperformance package, and that PYTHON_JIT=0 disables the JIT (verify against your build's docs):

python -m pip install pyperformance
PYTHON_JIT=0 pyperformance run -o jit_off.json
PYTHON_JIT=1 pyperformance run -o jit_on.json
pyperformance compare jit_off.json jit_on.json

Then repeat on your own service: run a representative request replay at least five times per mode on an otherwise idle machine, pin CPU frequency where you can, and record median latency, p99, startup time, warmup time and resident memory. The PEP reviewers asked for startup, warmup and memory metrics that the draft did not define, so those are exactly the numbers to collect.

Limitations and failure modes

  • Gains concentrate in long-running tight loops, per InfoWorld; short-lived scripts pay compile and warmup cost.
  • Profilers and debuggers: expanded native-profiler and debugger testing was a year-one goal, which implies the tooling story was not finished.
  • Build dependency on LLVM and a possible removal from main mean a long-term fork or alternative build is the only path for people who depend on it.

Engineering recommendations

  • Do not plan capacity around the JIT; at 4 to 12 percent on a benchmark suite, a profile-driven fix in your own hot path is likelier to pay more.
  • If you tried the JIT on 3.13 to 3.15, record the result now, since the feature is expected to leave main.
  • If you need large speedups, evaluate PyPy compatibility with your extensions or move the hot loop to native code.

Conclusion

The JIT is technically coherent, and its measured gain was real but modest. The withdrawal is a project-governance outcome: no one had the capacity to meet the Council's maintenance and evidence bar. Next step: run the benchmark sketch above against your own workload and write down whether the difference exceeds your run-to-run noise.

Sources

I read the PEP page and two news reports and ran no benchmarks; the benchmark figures are the PEP authors' and the Council's reasons and removal timing come from secondary reporting, not the resolution post.

Share:

Leave a comment

Explore More

Discover More Articles

Browse Articles
↑Zalo
CPython's Copy-and-Patch JIT After PEP 836: What Was Measured and Why It Was Withdrawn | Aloka Technology