mypyc Compilation in Production: Speedups That Survive Real Imports

mypyc Compilation in Production: Speedups That Survive Real Imports

mypyc compiles typed Python to C extensions that import like ordinary modules — until the wheel does not match the runtime, a dynamic pattern escapes compilation, or cold start grows from giant .so loads. Used well, it accelerates hot pure-Python modules without a rewrite. Used carelessly, it breaks imports on one AMI while laptops stay green.

⚡ TL;DR: Compile a small allowlist of typed modules. Measure import time and p99. Publish platform wheels; keep .py fallbacks. Avoid compiling glue that uses heavy dynamism. Pair with PyO3 for true native hot loops when mypyc plateaus.

Allowlist modules that are actually hot

# mypyc.ini (illustrative)
[mypy]
python_version = 3.12
strict = True

# Only compile these — not the whole monorepo
# setup.cfg / maturin-style packaging lists:
#   mypyc_modules =
#     billing.tax
#     billing.proration
# billing/tax.py — keep types precise for mypyc
from __future__ import annotations

def tax_cents(amount_cents: int, bps: int) -> int:
    # ✅ Simple typed arithmetic compiles well
    return (amount_cents * bps) // 10_000

❌ Compiling modules full of getattr, dynamic imports, and ORM magic — you pay compile pain for little gain.

Import-time and correctness gates

# scripts/check_mypyc_import.py
import importlib, os, time

def timed_import(name: str) -> float:
    t0 = time.perf_counter()
    importlib.import_module(name)
    return time.perf_counter() - t0

if __name__ == "__main__":
    # ✅ Fail CI if compiled import regresses badly vs baseline artifact
    sec = timed_import(os.environ["MODULE"])
    print(f"import_s={sec:.4f}")
    if sec > float(os.environ.get("MAX_IMPORT_S", "0.25")):
        raise SystemExit("import_too_slow")

Run unit tests against both pure-Python and mypyc-built wheels in CI. Prefer identical results over microbenchmark bragging rights.

Packaging reality

Risk Control
ABI skew (3.11 wheel on 3.12) Tag wheels; fail deploy on mismatch
Missing musllinux wheel Build both manylinux + musllinux
Debug nightmare Keep uncommitted .py readable; map lines in crashes
Oversized extension Split allowlist; don’t compile tests
# ✅ Build in CI matching runtime
python -m pip install mypy mypyc build
python setup.py build_ext --inplace   # or your maturin/setuptools flow
pytest -q
auditwheel repair dist/*.whl  # linux

When to stop and use PyO3 instead

If flamegraphs still show C-level need (tight numeric loops, custom parsing), mypyc may plateau. Escalate that one function to Rust via PyO3 rather than forcing more of the app through mypyc.

Closing checklist

✅ Dos
– ✅ Allowlist a few typed hot modules
– ✅ Dual-test pure Python and compiled wheels
– ✅ Gate import latency in CI
– ✅ Publish wheels for every prod platform
– ✅ Keep readable fallbacks and rollback tags

❌ Don’ts
– ❌ Don’t mypyc the entire monorepo on day one
– ❌ Don’t deploy mismatched Python minor ABIs
– ❌ Don’t expect gains from highly dynamic code
– ❌ Don’t skip musllinux if you run Alpine/ECS scratch
– ❌ Don’t treat compile success as a performance win without metrics

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply