Skip to content

What is CPython's JIT Compiler?

CPython’s JIT compiler is an experimental feature, introduced in Python 3.13, that translates frequently executed Python bytecode into machine code while a program runs. It is off by default and requires no changes to Python source code. Its goal is to make CPython faster over successive releases without asking developers to switch runtimes or rewrite anything.

How CPython runs code without the JIT

CPython compiles .py files into bytecode, a compact set of instructions stored in .pyc files. The interpreter then executes those instructions one at a time in a loop: fetch the next bytecode, dispatch to the C function that implements it, repeat. Every Python program pays the cost of that fetch-and-dispatch cycle on every instruction.

Python 3.11 added a specializing adaptive interpreter that watches which types flow through hot bytecode and swaps generic instructions for type-specific ones. When an addition operation keeps seeing integers, the interpreter replaces the generic handler with one that skips type-checking overhead. This technique (called “quickening”) sped up CPython by 10-25% across releases without any JIT.

The JIT builds on that specialization. Instead of interpreting the specialized bytecodes one at a time, the JIT lowers sequences of them into an intermediate representation called Tier 2 micro-ops, optimizes that representation, and then compiles it into native machine code that the CPU runs directly.

How copy-and-patch works

Traditional JIT compilers (like the one in PyPy or in Java’s HotSpot) analyze runtime behavior and generate optimized machine code through compilation pipelines. That approach produces fast code but requires a large compiler embedded in the runtime.

CPython’s JIT uses a technique called copy-and-patch. At CPython’s build time (not at your program’s runtime), LLVM compiles each Tier 2 micro-op handler into a small chunk of machine code called a stencil. These stencils are baked into the CPython binary. When the JIT activates at runtime, it copies the relevant stencils into a buffer and patches in the runtime-specific values (memory addresses, constants) that each stencil needs. The result is a block of native machine code assembled from prefabricated parts.

This trades optimization depth for compilation speed. A traditional JIT can rearrange, inline, and eliminate code across large regions of a program. Copy-and-patch stitches together pre-built pieces, so the machine code it produces is less optimized. But the assembly step is so fast that CPython spends more time allocating memory for the code buffer than it does filling it.

PEP 744: JIT Compilation documents the design decisions and the criteria for the JIT to become a permanent, non-experimental feature.

How the JIT differs from PyPy

PyPy has had a mature tracing JIT for over a decade, delivering 4-10x speedups on long-running, loop-heavy Python programs. The two JITs differ in scope and approach:

  • PyPy traces execution paths across function boundaries and compiles entire hot loops into optimized machine code. CPython’s JIT compiles smaller units (sequences of micro-ops) with less cross-function analysis. On benchmarks where PyPy excels, it can be 5-10x faster than CPython. CPython’s JIT targets single-digit percentage gains in its current phase, with larger optimizations planned for future releases.
  • PyPy is a separate runtime with its own garbage collector and C extension compatibility layer (which does not support all C extensions). CPython’s JIT ships inside CPython itself, so C extensions and profilers that work with CPython today continue to work. (Some low-level debugger features around JIT-compiled frames are still maturing.)

For most Python developers, the practical difference is that CPython’s JIT requires no runtime switch. It improves the interpreter you already use.

How the JIT performs today

The JIT’s track record is short and honest. In Python 3.13 and 3.14, the JIT was often slower than the interpreter on many benchmarks, sometimes by enough to make enabling it counterproductive. The team prioritized building contributor capacity and compiler infrastructure over immediate speed gains during that period.

Python 3.15 alpha turned the corner. Measured across the pyperformance benchmark suite, the JIT runs 5-6% faster than the standard interpreter on x86_64 Linux and 11-12% faster on macOS AArch64. Two optimizations drove most of that gain: expanded trace recording (covering 50% more code paths) and reference count elimination in JIT-compiled code.

Results vary by workload. Some benchmarks see 20%+ speedups; others still show slowdowns. The geometric mean (a single summary number across all benchmarks) is what the core team tracks, and it crossed into positive territory for the first time in the 3.15 cycle.

The core team’s public roadmap targets a 10% geometric-mean speedup by Python 3.16, along with JIT support for the free-threaded build. The site doesjitgobrrr.com runs daily pyperformance benchmarks across multiple machines and publishes the results, giving a live view of progress between releases.

Why most developers can ignore this (for now)

The JIT is experimental. It is disabled by default in every released CPython and requires the --enable-experimental-jit build flag at compile time. Pre-built Python installers from python.org, Homebrew, and uv do not include it.

No Python source code needs to change to benefit from the JIT once it ships as a default. When the JIT eventually graduates from experimental status, the speedup will arrive as a free upgrade through a normal Python version bump. Until then, the JIT is relevant to CPython contributors and to developers who build CPython from source and want to benchmark their own workloads against it.

Learn More

Last updated on

Please submit corrections and feedback...