Benchmarks
74,300 tasks/sec end to end at 51 µs median, with zero memory growth over 443,912 tasks. Every figure here was measured on one machine with the harness in bench/, and anything that was not measured is labelled with its provenance.
bench/run.sh. Numbers that were not measured here appear only in the comparison table at the bottom, and are labelled with their provenance every time they appear.Reference run
| Configuration | |
|---|---|
| machine | Apple M4 Pro (12 cores), 24 GB |
| os | macOS 26.5.1, arm64 |
| build | Apple clang 17, quicxd built -O2 |
| allocator | Default 8 MB PMAD pool |
| transport | Loopback TCP, no network hop |
| load | 8 worker processes, 8 producer connections, C clients |
send_frame_v fix is unverifiable and has been removed from this site. protocol.c wrote one struct iovec past the end of a stack array on every task dispatch that carried a payload — undefined behaviour. One build aborted on the first task; another silently corrupted adjacent stack slots and kept running. A measurement taken before the fix may be fine or may be garbage, and there is no way to tell retroactively, so everything was retaken on a fixed build. The fix itself is free: 46.8k/s fixed versus 46.4k/s unfixed at identical settings.Throughput
74,300 tasks/sec — end to end, single thread. Closed loop, 8 connections × 32 in flight, 8 workers. 443,912 tasks in the measured window, 0 errors, 0 dropped connections. Repeat runs land between 74k and 75k.
One Quicx task is a full lifecycle: producer submits → daemon acks → daemon dispatches → worker parses the payload → worker reports done → producer is notified. Five protocol messages. This is not a single enqueue operation, and it should not be compared against one.
Latency
Open loop at 40,000 tasks/sec offered. Tasks are due on a fixed schedule and latency is measured from the scheduled time, so any stall is charged in full rather than hidden by a client that slows down along with the server.
| Percentile | submit → done |
|---|---|
| p50 | 51 µs |
| p90 | 67 µs |
| p99 | 111 µs |
| p99.9 | 174 µs |
Latency stays flat from 10k/s to 40k/s — the daemon is not straining anywhere in that range.
| offered | p50 | p99 | p99.9 |
|---|---|---|---|
| 10k/s | 52 µs | 116 µs | 157 µs |
| 20k/s | 52 µs | 99 µs | 163 µs |
| 40k/s | 51 µs | 111 µs | 174 µs |
Memory
Idle RSS 9,584 KB. After 443,912 tasks: 9,584 KB. Delta: 0 KB.
Not “low growth” — no growth. The pool is mapped once at startup and every allocation comes out of it. quicx status reports live usage per size class, so you can see exactly which classes your workload consumes. At the default 8 MB pool under this load, peak usage was 10.7% of the pool.
Footprint
| Value | |
|---|---|
| binary | 54.4 KB (-O2, stripped of nothing) |
| runtime dependencies | none |
| source | ~2,480 lines (of C) |
| threads | 1 |
Methodology
The harness lives in bench/ and you can run it yourself. The choices below are the difference between a benchmark and a marketing claim.
- C clients, not Python
- A Python client tops out around 50–100k socket ops/sec and contends on the GIL across threads, so it saturates before the daemon does and ends up measuring itself.
- Latency is submit → done
- Not submit → ack. An ack only means the daemon received the frame; it never touches a worker. For a task queue, the number that matters is when the work is finished.
- Open loop for latency, closed loop for throughput
- Latency runs put tasks on a fixed schedule and charge stalls against the scheduled send time — the standard correction for coordinated omission. Closed-loop latency is not quoted, because a client that backs off when the server slows cannot measure the server slowing.
- Real payloads, really parsed
- nginx combined-format access-log lines, 120–260 bytes. Workers parse them — IP, method, path hash, status, bytes — and report per-status totals, so a run can be verified rather than trusted.
- One machine, loopback TCP
- No network hop. Real deployments will be slower.
- The harness checks itself
- 256 outstanding requests ÷ 74.3k/s = 3.4 ms, and measured closed-loop p50 was 3.6 ms. Little's Law agrees, so the instrument is measuring queueing and not itself.
Comparison with other queues
| throughput | latency | provenance | |
|---|---|---|---|
| Quicx | 74.3k tasks/s (1 thread) | 51 µs p50 end-to-end | measured here |
| Redis (LPUSH) | ~161k commands/s | 0.30 ms p50, one command | reported |
| RabbitMQ | ~40k msg/s | — | reported |
| Kafka | >1M msg/s, batched | ~ms | reported |
| beanstalkd | — | — | no comparable figure found |
LPUSH is one command — and a real Redis job queue needs LPUSH + BRPOP+ an ack. Normalised per completed job, Redis’s ~161k commands/sec is on the order of 50k jobs/sec. Putting 74.3k tasks/s next to 161k LPUSH/s directly would flatter Redis by roughly 3×.Part of Quicx’s latency advantage is work it is not doing. Before you take these numbers as a reason to switch, read Limitations — it lists exactly which work that is.