Skip to content
← All articles

The CPython JIT: an honest status report

It is off by default in release binaries, it does not work on the free-threaded build you also want, and on 3.14 the documented range is 10% slower to 20% faster. 3.15's tracing frontend is a genuine step change at 8-9% geomean — with a per-benchmark spread from a 15% slowdown to over 100% speedup, and a not-yet-final note attached.

A team reads “Python 3.15 is 10% faster thanks to the JIT”, plans next year’s capacity on it, and then discovers three things in sequence: it is off by default in the binaries they install, it is mutually exclusive with the free-threading they also wanted, and on their workload it is slower.

This article is the version of the story you can plan with.

What shipped, when, and what the numbers actually were

PEP 744 (3.13) introduced a copy-and-patch JIT and was unusually candid about it. The PEP itself says the JIT is “about as fast as the existing specializing interpreter on most platforms” and that it “isn’t yet a clear win when always enabled”, at a cost of “10-20% more memory”. That is a foundation to build on, not a speedup to deploy.

3.14 shipped it in the official macOS and Windows binaries, off by default, and documented the range plainly: “the typical performance impact of enabling it can range from 10% slower to 20% faster.” Read that as a distribution, not a headline. If you enable it, some of your services get slower.

3.15 is a genuine step change. A new tracing frontend records actual execution paths rather than working from static structure, plus basic register allocation, reference-count elimination and GDB unwinding support. The measured result: 8-9% geometric mean on x86-64 Linux, 12-13% on AArch64 macOS.

And the What’s New attaches, verbatim, .. attention:: These results are not yet final. The per-benchmark spread is “roughly 15% slowdown to over 100% speedup” — so the geomean is a summary of a very wide distribution, and your workload is one draw from it.

How to check, and how to enable

import sys

sys._jit.is_available()   # was this interpreter built with JIT support?
sys._jit.is_enabled()     # is it turned on?
sys._jit.is_active()      # is it compiling right now?

The documentation labels these “an experimental implementation detail”, and the leading underscore is not decoration. Use them for a startup log line and for deciding whether a benchmark result is meaningful; do not branch application logic on them.

Enable with the environment variable:

PYTHON_JIT=1 python app.py

The two facts that decide it for most teams

Free-threaded builds do not support JIT compilation. This is in the 3.14 documentation, and it is the constraint that matters most, because the teams most interested in a 10% single-thread win are frequently the same teams interested in removing the GIL. You choose one. For a workload that is genuinely parallel, free-threading’s multi-core scaling dwarfs a single-digit interpreter win; for a single-threaded workload the JIT is the only one of the two that does anything.

The Faster CPython team lost its main sponsor in 2025. That does not stop the work — it continues, and 3.15’s numbers are real — but it does change the pace you should assume for anything not already merged. Roadmap slides are not a capacity plan.

💡Your team is choosing between planning for the JIT and planning for free-threading. What does the decision actually depend on? click to reveal

On whether your bottleneck is single-thread interpreter speed or multi-core parallelism, and that is an empirical question with a cheap answer.

If your service is I/O-bound — most web services — neither helps much. The time is in the database and the network, and a 10% interpreter win on 15% of the request is 1.5%. Profile first (article 11.19); if Python bytecode execution is not a large share of your latency, this entire question is a distraction.

If you are CPU-bound and single-threaded — a batch job, a parser, a serialisation-heavy path — the JIT is the only one of the two that can help, and the honest expectation is “a few percent, possibly negative, measure it”.

If you are CPU-bound and parallelisable — many independent items, no shared mutable state — free-threading offers a multiple, not a percentage, and that is not a close comparison. The cost is real: your C extensions need the Py_mod_gil slot or the GIL silently comes back (with a warning), you pay 5-10% single-thread overhead and 15-20% more memory, and you need explicit locks around every multi-step invariant.

The meta-answer, and the one worth actually acting on: neither is a plan. Both are single-digit-to-small-multiple changes to a constant factor. A better algorithm, a better data layout, or removing an N+1 query are all still available and all still bigger. Reach for the runtime only when the code is already right.

What to write down

If you are going to have this conversation more than once, log the facts at startup: sys.version, sys._jit.is_enabled(), and sysconfig.get_config_var("Py_GIL_DISABLED"). Emit them as metric labels too. Then when someone claims “we’re on the JIT build and it’s slower”, it is a fact you can check rather than an argument you have to have.