Benchmarks
2026-10-06 — Development server: release-mode comparison and request-path work
Host: VMware VM, AMD EPYC 7642, 8 vCPU, 31 GiB, Ubuntu 24.04, kernel 6.8 (no WSL or
Docker). Server CPUs 0-1, applications 2-3, oha 4-7; 64 connections. Release mode
(MODE=release bench/multi.sh): 10 s warm-up, 30 s measurement, 5 runs, medians; every
process with ulimit -n 65535; open file cache with a check per hit and kTLS on both
servers; nginx sends the same forwarding headers as scb.
Result (requests/s, median of 5)
scenario scb nginx scb/nginx CV% scb scb cpu_us/req nginx
static-small 73.7k 74.5k 0.99× 1.6 27.1 26.8
static-1m 7.9k 9.0k 0.88× 3.4 252.2 223.1
respond 97.4k 111.8k 0.87× 3.8 20.5 17.9
proxy 47.4k 52.8k 0.90× 2.2 42.1 37.9
cache-hit 82.4k 69.0k 1.19× 7.9 24.3 28.9
tls-h2-static-small 64.2k 44.6k 1.44× 3.4 31.1 44.8
tls-static-1m 1.57k 1.56k 1.00× 1.4 1272.8 1277.4
php 29.7k 26.8k 1.11× 5.6 59.8 68.3
node 21.8k 26.0k 0.84× 3.4 59.4 52.4
python 11.6k 12.2k 0.96× 3.8 92.3 80.7Peak RSS: scb 33–42 MiB, nginx 42–50 MiB. After this run: MSG_MORE for the response head
before sendfile (below) moved static-1m to ~0.91×.
Findings and changes (each verified by an independent A/B)
| Change | A/B result |
|---|---|
| Node “regression” | none: same build pair 8 × 30 s, 29.9k vs 29.8k. Node is single-threaded and saturated; both builds deliver identical requests; upstream reuse 99.99 %, no pool starvation |
| FastCGI connection drivers (ADR-0032) | PHP 17.2–20.4k → 23.6–26.9k; FPM workers busy 1.2–1.3 → 1.4–1.5 CPUs |
| Drivers on a dedicated runtime | worse in 4 alternating runs — reverted |
| Precomputed static file headers, shared cache key | user CPU 9.3 → 7.6 µs/req, 67k → 78k |
| Proxy: no URI re-parse, single-pass header stripping, replay copy only after ≥1 s idle | user CPU 13.5 → 12.4 µs/req; allocations 47 → 36/req |
Response head with MSG_MORE before sendfile | segments 23.3 → 21.7 per 1 MiB response (nginx 20.8); 7.7k → 8.2k, CPU 259 → 243 µs/req |
Cache-hit flagged by compare.py (−9.9 % against the morning’s 3 short runs) | not a regression: old vs new build 6 × 30 s, 86.4k vs 85.3k, CPU 23.1 vs 23.4 µs/req |
Where the remaining gaps are:
- respond and proxy: entirely user space (kernel time equal or lower than nginx’s; same syscalls and TCP segments per request). Proxy user CPU 12.4 vs ~8.7 µs.
- static-1m: ~1 more TCP segment per response than nginx and ~6 µs more user time.
- node: Node.js itself is the bottleneck; results vary by ±10 % run to run.