compiler-category Beginner 10 min read

What AOT Compilation Means for Python

Interpreters, JITs and AOT compilers for Python: how each works, when each wins, and where Pyvorin Native 1.0.9's local compilation fits.

Published Aug 26, 2026

Ahead-of-time compilation turns Python source into machine code before the program runs, rather than translating it instruction by instruction while the program executes. That single shift removes the interpreter from the hot path: no bytecode dispatch, no per-operation overhead, just the CPU running the loop the compiler produced. Pyvorin Native 1.0.9 is an AOT compiler of this kind — local, in-process, and selective about which functions it takes — and measured across our 71-workload suite its trade produced 54 workloads faster than CPython and 17 slower, with a geometric mean of 3.16x and a median of 1.35x. This page explains what AOT actually means for a Python program, how it differs from the interpreter and from just-in-time compilation, and where the honest limits sit.

Nothing below requires compiler background. The one idea to hold onto: the cost of interpretation is paid per operation, every time, forever. Everything else follows from that.

The three ways Python gets executed

Every Python program runs through one of three execution strategies, and most production systems use more than one at once.

An interpreter reads bytecode and executes it operation by operation. CPython works this way: it compiles your source to bytecode ahead of time, but the bytecode is still executed by a dispatch loop at runtime. Every addition, comparison and attribute lookup pays the cost of that loop. The upside is total flexibility — any construct the language allows can run, in any order, with full introspection — and it is why CPython remains the compatibility reference every other implementation is judged against.

A just-in-time compiler waits until the program is running, watches which code gets hot, and compiles that code to machine code during execution. The JIT sees real types and real branch behaviour, so it can make excellent guesses. The price is paid up front in warm-up: the first stretch of execution runs interpreted while the JIT gathers evidence, and the compilation itself consumes CPU that the program could have used. PyPy applies this strategy across a whole program with a meta-tracing JIT; Numba applies it to individual numeric functions.

An ahead-of-time compiler compiles before execution, on a schedule the operator controls — at install time, at deploy time, or on first import. There is no warm-up: the first call to a compiled function runs machine code immediately, at the cost of a compile step that happened earlier and whose time must be amortised across the calls that follow. Pyvorin compiles selected functions this way, locally and in-process, lowering them to LLVM IR through llvmlite and linking a shared library that loads into your running CPython process.

What the interpreter is actually spending time on

The case for either kind of compilation rests on three costs that interpretation pays on every operation. Understanding them tells you where compilation can help and where it cannot.

Dispatch. Each bytecode instruction costs a branch through the interpreter loop — decode, dispatch, execute, repeat. A tight numeric loop in pure Python might execute hundreds of millions of bytecodes where the equivalent machine code runs a fraction of the instructions. Dispatch is the largest and most removable cost.

Boxing. In CPython every value is an object, even a small integer. Arithmetic on two floats allocates or recycles heap objects and routes through generic operator machinery. Native code keeps values in registers, unboxed, when type inference proves that is safe.

Reference counting. Every object carries a count of owners, updated on every assignment, every argument pass, every return. In object-churning code — building and discarding millions of small strings or dicts — reference counting can rival dispatch as a cost. It is also the hardest of the three to remove, because it is entangled with CPython's memory model and C API; even compiled code that still allocates Python objects keeps paying it.

The pattern in our measured results tracks these three costs closely. Workloads dominated by dispatch-heavy pure-Python loops — numerical computing at a 10.91x category geomean, core integer and loop kernels at 16.24x, object manipulation at 42.28x — gain the most. Workloads dominated by reference-count churn and calls into C libraries gain little or regress: string manipulation at 0.85x and parsing at 0.86x sit fractionally below the interpreter they started on. A compiler removes dispatch efficiently. It cannot remove the cost of code that was never dispatch-bound in the first place.

Where AOT fits, and where it does not

Ahead-of-time compilation earns its keep where four conditions overlap: the workload is CPU-bound, the hot code is pure Python rather than library calls, the functions run long enough or often enough to amortise the compile step, and the compile step itself can happen at a time that suits you. Batch pipelines, simulations, financial calculations and sensor aggregations fit this shape well — which is precisely the top of our measured table.

AOT is a poor fit where the program's time goes to I/O, waiting, or C extensions that are already native code. A web request handler that spends its life parsing headers and calling a database driver has little interpreted loop left to reclaim; our web category measured a 0.97x geomean, in effect a wash. Short-lived micro-workloads are the worst case: at 0.05x, the suite's smallest workload ran slower under Pyvorin because any fixed overhead dwarfs a loop that barely exists.

The honest summary is a distribution, not a promise: best 202.83x, median 1.35x, worst 0.05x, across 71 workloads. Where your code lands in that distribution depends on how much of its runtime is acceleratable pure-Python execution — the only way to know is to measure, which is what the benchmark command at the bottom of this page is for.

AOT and JIT compared

The two compiled strategies are better thought of as answers to different questions. A JIT asks: "which code is hot right now, and what are its real types?" An AOT compiler asks: "which code will be hot, and what can I prove about it before it runs?" Each question has a setting where it is the right one.

DimensionAhead-of-timeJust-in-time
When compilation happensBefore execution, on the operator's scheduleDuring execution, once code proves hot
First-call costNear zero after the compile step; compile time amortises separatelyInterpreted until the JIT tier kicks in
Type informationInferred statically; guarded at runtime, with deoptimisation on violationObserved from real executions; speculatively specialised
Warm-up behaviourNone — compiled or not from the first callWorkload-dependent; can be seconds on large codebases
Peak performance ceilingHigh on provably typed loops; bounded by what static analysis can proveHigh on stable, repetitive workloads with stable types
Best environmentBatch jobs, CLIs, anything cold-start-sensitive or short-livedLong-running servers with stable hot loops

CPython itself has entered this space with an experimental JIT, introduced in Python 3.13 under PEP 744 and disabled by default. It is a copy-and-patch design, and the project's own release notes describe the performance improvement as modest. It is a foundation, not a finished win — and its existence changes nothing about the structural point above: dispatch cost is inherent to interpretation, and only compilation removes it.

Where Pyvorin sits

Pyvorin's design choices follow directly from the AOT column of that table, with one addition that matters in practice: it is selective and honest about failure. Compilation happens locally, in your process, from your existing source — no separate build language, no annotations, no network involvement in the compile path. Type inference runs over each candidate function; functions that pass are lowered through LLVM and cached on disk keyed by a hash of source and options, so unchanged code is never recompiled. Functions that fail the compatibility gate are not rejected — they are marked and continue to run under ordinary CPython semantics, with the fallback recorded in the run report. Every benchmark run also verifies the compiled result against the interpreter's output before any timing is trusted.

The selection granularity is the function, not the program. That is a deliberate engineering judgement: it lets a mixed codebase keep its parsing, I/O and framework glue on the interpreter while its numeric core runs natively, without anyone re-architecting the seam between them. The trade-offs of that choice, and the machinery behind it, are covered in Compiler pipeline overview.

AOT among the compiled-Python tools

Ahead-of-time compilation is not one product category but several, and the differences matter when you shortlist tools. Cython translates a Python-like dialect, with optional type annotations, into C source that is then compiled — powerful and mature, but it asks you to write and maintain a second source form. Nuitka compiles whole Python programs ahead of time through C, aiming at deployment as a standalone binary rather than accelerating selected functions inside an otherwise ordinary CPython process. Numba is a JIT rather than an AOT compiler: it compiles decorated numeric functions at first call and falls back per function where its type subset does not fit. Pyvorin's position is the remaining quadrant: AOT compilation of selected functions from unmodified Python source, executed inside your existing CPython process, beside the interpreter rather than instead of it. The comparisons page Pyvorin vs CPython works through what that placement preserves and what it gives up.

How to judge an AOT compiler's claims

Whatever AOT route you evaluate, the evidence standard is the same, and it is worth stating before any benchmark table appears. Insist on per-workload results rather than a single aggregate — our own suite's geomean of 3.16x would be a lie if it were presented without the median of 1.35x and the seventeen slower workloads beside it. Insist that losses be published with the wins, because the losses carry the shape information that tells you whether your code fits. Insist that correctness be checked against the interpreter's output on every run, and that compile time be reported separately from execution time so amortisation can be judged honestly. A compiler whose marketing cannot survive those four requirements will not survive contact with your codebase either.

Honest limits

Three limits deserve plain statement. First, AOT compilation is not a compatibility guarantee: some Python constructs do not currently lower to native code, and where compilation cannot hold, Pyvorin falls back to CPython rather than guess — semantics are never traded for speed. Second, the compile step is real time — measured in the low hundreds of milliseconds per function on our walkthrough host — which is worth it for a batch job and pointless for a one-shot script. Third, seventeen of the suite's seventy-one workloads ran slower under Pyvorin, and the pattern is predictable: string, parsing, web and compression shapes, where the interpreter was never the bottleneck. For those shapes the honest answer is not to compile.

Where to go next

Last reviewed 26 August 2026 against Pyvorin Native 1.0.9 and the canonical benchmark artefact dated 13 September 2026 (71 workloads: 54 faster, 17 slower; geomean 3.16x, median 1.35x, best 202.83x, worst 0.05x). Figures on this page are extracted from the artefact, not typed by hand.