Python Killed Its JIT. What Makes Python Fast Now?
CPython is dropping its experimental copy-and-patch JIT — it never beat the interpreter by much, and it made you build with LLVM.
The experimental JIT that landed in CPython 3.13 is on its way out. If your mental model of Python's future included "eventually there's a JIT and my hot loops stop hurting" — that line item just got deleted.
The short version: it never shipped enabled by default, it required an LLVM toolchain at build time, and on the benchmarks maintainers actually ran, it hovered around break-even with the interpreter you already have. Two execution paths to keep correct forever, for a few percent. Bad trade. So it goes.
Why this matters
It settles where CPython's speed comes from. The 3.11–3.13 gains people actually noticed came from the specializing interpreter — bytecode that rewrites itself for the types it sees at runtime. That work stays. The JIT was a side bet that didn't pay out.
It also kills a planning assumption. Nobody should write "wait for the JIT" in a performance RFC again. If Python is slow today, the fix is the same fix it's been for twenty years: better algorithms, native libraries, or hot code moved out of the interpreter.
And it's a signal about priorities. The free-threaded (no-GIL) build is the project's expensive bet now; deleting the JIT frees the people who were maintaining two code paths.
How it works
The design was copy-and-patch. CPython lowers bytecode to micro-ops; at build time, LLVM precompiles one machine-code "stencil" per micro-op; at runtime, the JIT glues stencils together into a function. Cheap to implement — but there's no profiling tier, no speculative optimization, nothing like the layered compilers in V8 or HotSpot.
So you get machine code that isn't much smarter than the bytecode it replaced. Microbenchmarks could win. Real applications — I/O-bound services, NumPy-heavy pipelines — mostly treaded water, while every contributor paid the LLVM dependency and doubled testing surface.
Where this helps
- Perf roadmaps: budget for profiling and algorithm fixes on CPU-bound services, not a future flag that makes it all free.
- Data pipelines: hot loops already belong in NumPy, Polars, or Arrow. Those run native code with or without a JIT — nothing changes for you.
- Extension authors: Cython and PyO3 code bypasses the interpreter entirely; this removal doesn't touch that story.
- CI: if your build matrix had an
--enable-experimental-jitentry, delete it and drop the LLVM-shaped dependency.
Watch out
Don't conflate this with free-threading. The no-GIL build is a separate project and isn't going anywhere; this news says nothing about it.
If you genuinely need a JIT for pure Python today, PyPy still exists and is still good at exactly that job.
And if you benchmarked the JIT yourself and saw wins — check whether it was a microbenchmark. Tight numeric loops are copy-and-patch's best case and a poor stand-in for real workloads.
Try it yourself
The specializing interpreter is the engine that actually got faster. Watch it rewrite bytecode once a function runs hot:
import dis
def add_loop(n):
total = 0
for i in range(n):
total += i
return total
add_loop(5) # cold: generic BINARY_OP
add_loop(10_000_000) # warm: interpreter specializes the hot ops
dis.dis(add_loop)
# Look for specialized variants like BINARY_OP (add, int) —
# exact names vary by version. That's the fast path
# the Faster CPython work built, and the one that survives.TL;DR
- What changed: CPython is removing its experimental copy-and-patch JIT; the specializing interpreter stays.
- Why it matters: Python's speed was never going to come from that JIT — profile, vectorize, or go native instead of waiting for a flag.
- Try today: run the snippet and watch your bytecode specialize after warm-up. That's the engine that actually won.