1 binary
Native executable
No interpreter in the artifact; untyped imports route to an embedded CPython island only when you opt in
23.9x
Kernel speed vs CPython
Geomean over sum-squares, floor-sum, horner, repeat-len; peak RSS 5.8x smaller
13,798
Conformance probes
Nightly byte-compared against a pinned CPython 3.14.9 oracle
63
Numbered divergences
Every known behavior difference, documented with a recorded probe
The speed figure is pyscriptc's reproducible baseline against CPython3.14.9 (uv run python -m benchmarks.baseline.run --check), not a ranking against the tools below; none of them was benchmarked here.
What pyscriptc is not
pyscriptc compiles a typed subset of Python to native machine code. It is not a way to run untyped Python: code outside the subset stops the build with a PSxxxx diagnostic that names the exact line, or, when you opt in with --fallback=cpython, runs on an embedded CPython instead. It is not a drop-in CPython replacement: the subset has 63 numbered divergences, each documented with a recorded probe. It is not an interpreter accelerator: nothing here speeds up code in a running Python process, because nothing here runs inside one.
The input contract is annotations. Types steer the lowering, and a program the checker cannot type does not compile. That is the price; the artifact is the point: a binary with no interpreter in it.
Why not the alternatives?
Each block states what the tool is, where it wins, and what the artifact difference is. None of them is a strawman: each one is the right answer for someone.
Why not PyInstaller / cx_Freeze?
PyInstaller and cx_Freeze are bundlers, not compilers: they package CPython and your bytecode into a folder or a single executable, so the program runs on machines without Python installed, with CPython's behavior exactly, because it is CPython. If your program is untyped, dynamic, or simply existing, a bundler runs it and pyscriptc fences it. The trade is the artifact: a bundle carries a whole interpreter, so it is larger and starts slower than a binary with no interpreter in it.
Choose PyInstaller / cx_Freeze when: you need untyped Python packaged as an executable, unchanged.
Why not Nuitka?
Nuitka translates a whole Python program to C linked againstlibpython, so the result executes with CPython semantics across the whole language, no annotations needed, ending in a standalone folder or a onefile executable. It accepts everything; pyscriptc refuses most things and compiles the rest without an interpreter. pyscriptc's binaries are smaller because nolibpython rides along; its language coverage is far narrower.
Choose Nuitka when: your program is untyped or dynamic and must ship anyway.
Why not Cython?
Cython compiles a Python superset (its own .pyx dialect) to C extension modules that are imported back into a CPython session; static typing is optional and makes hot paths faster, and C/C++ interop is the traditional use. There is little overlap to compare: the output is a module for CPython to load, not a standalone program, and the input language is a dialect, not plain annotated Python. For speeding up one module inside a larger CPython application, Cython's shape is the one that fits.
Choose Cython when: you are accelerating one module inside a larger CPython application.
Why not mypyc?
mypyc compiles type-annotated Python modules to CPython C extensions, using mypy for checking; this is the closest input contract to pyscriptc's, and mypy itself uses it to speed up mypy. The difference is the artifact: mypyc code lives where CPython lives and keeps CPython semantics, while pyscriptc leaves the process entirely. If your goal is a faster module in an existing interpreter deployment, mypyc is the smaller step.
Choose mypyc when: your code is already typed and stays deployed inside CPython.
Why not Codon?
Codon is the closest sibling: an ahead-of-time compiler for a Python subset that produces native executables with no libpythoninside, inferring types instead of requiring them. The difference is what anchors the subset: Codon states in its own documentation that it is not a drop-in CPython replacement (int is 64-bit, numerics follow C semantics, monkey-patching is out), while pyscriptc pins CPython 3.14.x as the oracle and byte-compares compiled programs against it on the 13,798-probe corpus. The honest cost, stated plainly: in places Codon covers more of the standard library than pyscriptc does.
Choose Codon when: the workload is numeric and its C-style numerics are what you want.
Why not PyPy?
PyPy runs standard Python on its own JIT-compiling virtual machine: no annotations, no build step, a drop-in interpreter replacement, with C extensions as its known weak spot. pyscriptc is ahead-of-time and whole-program; the compile happens once, before distribution, and it refuses at compile time the dynamic patterns PyPy handles at run time. If the deployment model "install an interpreter and run faster" works for you, PyPy asks far less of your code than pyscriptc does.
Choose PyPy when: installing a faster interpreter is fine and a build step is not.
The numbers, with the receipt
23.9x
Kernels, wall time vs CPython 3.14.9
sum-squares, floor-sum, horner, repeat-len
5.8x
Kernels, peak RSS vs CPython 3.14.9
geomean of native / CPython RSS over the same rows
14.4x
Stdlib workloads, wall time vs CPython 3.14.9
25 standard-library call workloads
Reproduce it: uv run python -m benchmarks.baseline.run --check. Reference recorded on one Linux x86_64 host, 5 runs per sample, geometric means per workload group. This is pyscriptc's baseline against CPython, not a cross-tool ranking; the other tools on this page were not benchmarked, and their numbers live in their own documentation. The per-module and per-package conformance detail is on the compat site.
What shipping looks like
Compile once on the machine that has the toolchain:
pyscriptc compile hello.py --emit bin -o hello
scp hello box:/usr/local/bin/hello
On the box: ./hello. No venv to create, no container layer to pull, no libpython to install: a binary with no island in it carries no interpreter, so it runs on a machine with nothing Python on it. The same program under CPython means shipping an interpreter or depending on one being there, which is exactly what a bundler or Nuitka artifact also carries inside. What you give up is in the fences: code outside the subset does not compile, and an--island build that routes untyped imports to an embedded CPython is no longer interpreter-free.
Decision table
Rows are the tools; the columns are narrow by design, defined in the script block above. "Partial" marks a shape with a caveat spelled out in the sections above. The oracle column is about measurement, not compatibility: running on CPython or libpython is compatibility by construction, and the tools that document their deviations instead of probing them read "no" here. No cross-tool speed comparison is implied anywhere in the table.
When to choose what
- You need to ship existing, untyped Python as an executable, with full-language behavior: Nuitka, or a bundler if the size and startup cost of carrying CPython is acceptable.
- You need exact CPython behavior and accept shipping an interpreter: PyInstaller / cx_Freeze for distribution, PyPy if speed inside the runtime matters more than C extensions.
- You want to speed up one module inside a CPython process: Cython for C interop and hot paths, mypyc if the code is already typed.
- You want a native binary from statically inferable numeric code and accept its numerics: Codon.
- You have typed Python and want a native binary that measures its parity against CPython 3.14, names what it refuses, and delegates untyped imports to an embedded CPython: pyscriptc. Start with the compat site to see whether your modules compile today.
The durable version of this comparison, with sources for every tool, isthe comparison guide in the docs.