Runloom

Go-style stackful coroutines for Python. Write blocking code — fiber(fn),

plain recv/send, no async/await — and run a million of them across every

core in one process. Hand-rolled asm context switch + C work-stealing scheduler +

netpoll, built for free-threaded Python 3.14t (GIL off).

Runloom vs Go

Same box (64c, free-threaded CPython 3.13t), 8 hubs / GOMAXPROCS=8, warm

steady-state. Go ≈ 2.1 M spawn/s here.

The short story: on spawn, scheduling, and throughput, runloom trades blows

with Go and beats it on raw spawn — a stackful coroutine runtime on CPython

matching a compiled language even with a Python handler (596 k vs 603 k req/s at

saturation; a C handler beats Go). The one honest gap left is memory: a

suspended fiber carries a CPython eval frame, ~3.3× Go's per-fiber RSS.

Full cross-runtime numbers + cold spawn-vs-N curves: benchmark report

· perf summary.

benchmark report

perf summary

Install

Prebuilt wheels (no compiler needed) for CPython 3.11–3.14 on Linux

(x86_64/aarch64), macOS (arm64/x86_64), Windows (AMD64); source build elsewhere.

No runtime dependencies.

What it is

Hand-rolled asm context switch (x86_64 SysV, aarch64) — ~80 ns/swap, no

syscall; Windows Fibers / POSIX ucontext fallback.

M:N work-stealing scheduler (3.13t) — Chase-Lev deque per hub, per-hub MPSC

submission, woken goroutines routed back to their origin hub.

Per-goroutine PyThreadState snapshot — cframe, datastack, exc_info,

contextvars, recursion; a million yielded goroutines share their hub threads

with no frame-chain cliff.

netpoll — epoll / kqueue / IOCP / WSAPoll / select; goroutines park

transparently on fd readiness, lost-wake-free 3-state park-commit.

Go-style channels — Chan(capacity), select, for v in ch.

Stall isolation + recovery — one unanticipated blocking call stalls only

its hub, and the runtime detects + recovers it (default on, 3.13t).

monkey.patch() makes blocking stdlib (socket, time, threading, …)

cooperative, so existing blocking code runs unchanged.

Already have async def code? The runloom.aio bridge runs it on the

single-threaded scheduler (runloom.aio.run(main()) ≈ asyncio.run) — a

zero-rewrite port path, not a multi-core speedup (use the sync API with

run(n>1, main) for that).

Honest limitations

The multi-core win needs free-threaded CPython 3.13t (3.11+ for the frame

snapshot at all). On a GIL build runloom still runs — cheap spawn, the

goroutine model, netpoll — but single-core like asyncio.

runloom doesn't make Python faster per core. CPython's ~80 k pure-Python

ops/s/core is a constant it can't raise; it lets one process hit that on every

core at once with a blocking model. The scheduler itself is Go-class.

Higher memory per goroutine than Go (~3.3× for an empty fiber — the CPython

eval frame; a C handler closes most of it).

Preemption fires only at Python bytecode boundaries — a goroutine inside a

tight pure-C call (e.g. numpy) holds its hub until it returns (same as Go +

cgo).

Linux x86_64 / 3.13t is the primary, heavily-validated target (2 M-conn

runs, fuzzing, sanitizers, formal models); other backends are maintained

in-step but less deeply exercised.

Platform support

Docs & layout

Full guide in docs/:

Quickstart ·

Asyncio bridge ·

Sync API ·

Channels ·

M:N parallelism ·

Cookbook ·

API reference

docs/

Quickstart

Asyncio bridge

Sync API

Channels

M:N parallelism

Cookbook

API reference

Build from source (contributors): pip install -e . from a clone (needs a C

compiler; scripts/install.sh / scripts\install.bat bootstrap one). To hack on

runloom against free-threaded CPython, use a 3.13t interpreter.