DEV Community

Cover image for Python's JIT Is Being Removed After a 7-8% Gain. Here's What Makes Python Faster
Alan West
Alan West

Posted on Originally published at blog.authon.dev

Python's JIT Is Being Removed After a 7-8% Gain. Here's What Makes Python Faster

CPython's experimental JIT is being removed. It shipped in three releases (3.13, 3.14, 3.15), got a rewritten tracing frontend, register allocation and an LLVM 21 build dependency, and the official number on x86-64 Linux is a 7-8% geometric mean speedup. On October 19, 2026 the authors withdrew PEP 836, and the JIT code is expected to leave main early in the 3.16 cycle.

My take: good. The JIT was never going to fix slow Python, and the time teams spent waiting for it would have been better spent on things that already work. If you were counting on it, here's how it got pulled, what it means for PYTHON_JIT and your builds, and what I'd use instead.

How the CPython JIT ended up on the way out

The short version, from LWN's write-up:

  • Brandt Bucher merged the copy-and-patch JIT for 3.13 in early 2024 (PR #113465) without a PEP. PEP 744 came afterwards, and it was Informational, not Standards Track.
  • On June 5, 2026 the Steering Council froze new JIT work on main. Bug and security fixes were still allowed. The council gave six months for a Standards Track PEP to be submitted and resolved. If none was accepted, "the JIT code must be removed from the main branch."
  • On July 3 Bucher, Savannah Ostrowski and Ken Jin posted PEP 836, "JIT Go Brrr".
  • On September 21 the council replied with a ten-point list covering things like memory impact, platform parity, user impact, security ("an underserved area in the PEP"), and a compatibility review the PEP scheduled for the first 3.17 release candidate, around August 2028. The council called that "way, way too late."
  • On October 19 Ostrowski withdrew the PEP. Per LWN: "we don't feel we have the personal bandwidth to carry the current version forward."

So on paper this was a process decision. The JIT was paused, the authors were asked for a plan they could commit to, and they decided they couldn't carry it. The performance numbers are why nobody fought very hard to keep it.

The JIT's numbers never matched the hype

Here are the numbers the JIT's own docs and PEP published:

Release Official JIT claim Source
3.14 "can range from 10% slower to 20% faster, depending on workload" What's New 3.14
3.15 7-8% geomean on x86-64 Linux; 11-12% on AArch64 macOS vs the tail-calling interpreter What's New 3.15
3.15, per benchmark "roughly 15% slowdown to over 100% speedup" same
PEP 836 runs, June 2026 1.047x (Windows, Ryzen 5 3600X) to 1.126x (M3 Pro macOS) PEP 836

That 3.15 per-benchmark range means some benchmarks run 15% slower with the JIT on. If you run a web service, the geomean doesn't tell you much; you get whatever your own code paths get.

PEP 836 set its own target, and it asked for at least a 20% geomean improvement on pyperformance for JIT plus free-threading over the plain free-threaded interpreter, by the first 3.17 beta. The 3.15 number on the default x86-64 build was roughly a third of that. Different baseline, I know, but that's a big gap.

For comparison, the 3.14 tail-calling interpreter showed 3-5% on pyperformance in preliminary benchmarks, just from changing how the interpreter dispatches opcodes. It needs a recent compiler (Clang 19 at the time), but no new compiler tier inside CPython and no new block of runtime-generated machine code to secure.

The people who built it did serious work in 3.15: a trace-recording frontend, reference-count elimination, GDB unwinding through JIT frames. The problem is Python itself. As matklad pointed out on Lobsters, in Python "a lot of code that's being executed is the C code of the interpreter itself", and a bytecode JIT doesn't speed that up. It doesn't touch the C extensions most real programs lean on. Antonio Cuni, who has years of PyPy behind him, was blunter in the same thread: writing a Python implementation that is "real fast" and backwards compatible is "de facto impossible". He also says PyPy proves Python itself is optimizable, so his complaint is about compatibility, not the language.

What happens to PYTHON_JIT and your builds

  • On 3.13, 3.14 and 3.15 nothing changes. LWN reports the council said it was OK with the status quo for 3.15. The official macOS and Windows binaries ship with the JIT built but off, and PYTHON_JIT=1 turns it on.
  • If you build from source, --enable-experimental-jit (and =yes-off for "built but disabled") still works on those branches. You still need LLVM at build time; for 3.15 that's LLVM 21.
  • From 3.16 on, the code is expected to be removed from main early in the cycle, per LWN. Plan for the configure flag and the env var to do nothing, or to disappear.
  • The code itself isn't going away entirely. Ostrowski said the reference implementation will be published and linked from the withdrawn PEP. Brett Cannon said work stops in the main repo; whether it continues elsewhere is up to the community.

Check whether a given interpreter has it:

import sys

jit = getattr(sys, "_jit", None)
if jit is None:
    print("no sys._jit here (Python < 3.14)")
else:
    print("available:", jit.is_available(), "enabled:", jit.is_enabled())
Enter fullscreen mode Exit fullscreen mode

sys._jit was added in 3.14 and has a leading underscore, so it won't exist on 3.13 and probably won't survive 3.16. Don't build anything on it.

What actually makes Python faster in 2026

None of this is new, and all of it is less exciting than "Python gets a JIT".

Profile, then fix the algorithm

3.15 ships the Tachyon sampling profiler (PEP 799) as profiling.sampling, and the docs claim sampling rates "up to 1,000,000 Hz" with virtually zero overhead. On older versions, use py-spy. In my experience the hot spot is usually a quadratic loop or an N+1 query, and no JIT would have saved you from either.

Compile the hot module with mypyc, Cython or Numba

In the HN thread, one commenter reported a fib benchmark dropping from 1.45s to 0.064s with mypyc. If your code already has type hints, all mypyc costs you is a build step. For numeric loops, Numba is still the practical answer.

Move the hot core to Rust with PyO3

PyO3 and maturin made this boring, and boring is good. pydantic-core, Polars and orjson all keep Python at the surface and do the heavy lifting in Rust.

Free-threading for CPU-bound parallel work

3.14 made the free-threaded build officially supported, with a single-threaded penalty of roughly 5-10%. For CPU-bound work on threads, that beats the JIT's single-digit gain because it scales with cores. (In 3.14 you couldn't have both anyway: free-threaded builds don't support the JIT.) 3.15 adds an abi3t stable ABI for free-threaded extensions, which should shorten the "is my C dependency safe" wait. Try it:

uv python install 3.14t
uv run --python 3.14t python -c "import sys; print(sys._is_gil_enabled())"
Enter fullscreen mode Exit fullscreen mode

False means the GIL is really off. If an extension that doesn't declare free-threading support gets imported, CPython turns the GIL back on and warns you.

Startup time and tooling

3.15's explicit lazy import (PEP 810) and -X lazy_imports=all matter more for CLIs than any JIT ever would. On the tooling side, uv's README claims it is "10-100x faster" than pip, which is the kind of speedup your CI pipeline will actually notice. Bytecode dispatch was never what made it slow.

PyPy or GraalPy, if you can

Both show bigger speedups are possible, but HN commenters point out PyPy's newest supported version is 3.12 and loading arbitrary C extensions is still the weak spot.

Checklist if you were counting on the JIT

  1. Search CI configs, Dockerfiles and env files for PYTHON_JIT and --enable-experimental-jit. Keep them on 3.13-3.15 if you measured a win, and mark them for removal on 3.16.
  2. If you build CPython with the JIT, drop the LLVM build dependency when you move to 3.16.
  3. Rerun your own benchmarks with PYTHON_JIT=0. If the difference is under your normal noise, delete the setting today.
  4. Profile production with py-spy or Tachyon and find your top three hot functions.
  5. For each one, decide: algorithm fix, mypyc/Cython/Numba, or a Rust extension through PyO3.
  6. If you have CPU-bound threading work, test the 3.14t build and check your C extensions for free-threading support.
  7. Remove any sys._jit calls from code that has to run on 3.16.

Was the JIT a waste of time?

I don't think the code was wasted. The register allocation and unwinding work taught the core devs something, and the reference implementation will stay public. Where it did waste time was for teams who treated "the JIT is coming" as a reason not to profile, not to move hot code out of Python, or not to try free-threading.

Someone on Lobsters called the council's process "death by bureaucracy". Someone on HN called it "a pretty stellar example of non-bureaucratic governance". I'm with the second one. Pablo Galindo Salgado, who posted the announcement for the council, followed up that the six months was "a first goal to anchor, not a bureaucratic deadline we will hold", and that an extension could be requested. Writing down a maintenance plan is not a big ask for a whole new compiler tier.

So where do you stand? Should the core devs have kept going until they hit that 20% bar, or was a JIT always a dead end for CPython? And what actually made your Python faster: an algorithm fix, Rust, free-threading, or leaving Python?

Sources

Top comments (0)