Benchmarks

2026-10-05 — Docker Desktop smoke benchmark (M1 slice)

Not a performance claim. Docker Desktop on Windows (WSL2 VM), host shared with ~25 other running containers, no CPU frequency pinning. Use only to compare servers within this run and to find gross problems.

Environment

date: 2026-10-05T13:49:58Z
kernel: Linux 6.18.33.1-microsoft-standard-WSL2 x86_64 GNU/Linux
cpu_model: AMD Ryzen 7 7800X3D 8-Core Processor
cpus_online: 16
memory: 15Gi
server_cpus: 0-3 (4 workers)
upstream_cpus: 4-5
load_cpus: 6-11
duration_s: 10  warmup_s: 3  connections: 64
scb-webd: scb-webd 0.1.0
nginx: nginx version: nginx/1.22.1
caddy: 2.6.2
oha: oha 1.16.0
virtualization: 0::/

Command: docker run --rm --cpuset-cpus 0-11 -e DURATION=10 -v "$PWD/bench/results:/results" scb-bench (64 connections, 3 s warm-up, 10 s measured; server CPUs 0-3, upstream 4-5, oha 6-11).

Results

cpu_s = server CPU seconds during the 10 s window (max 40 for 4 CPUs). peak_rss_mib = peak resident memory of the server process tree.

server  scenario                     rps   p50_ms   p95_ms   p99_ms success_rate  non_2xx   cpu_s peak_rss_mib
scb     static-small               20208     3.08     5.57     6.94        1        0   28.28       15
scb     static-1m                   2147     29.3    39.52    46.09        1        0    28.1       42
scb     respond                    94943     0.54     1.61     2.55        1        0   24.99       42
scb     proxy                      48380     1.23     2.21     3.13        1        0   30.79       42
scb     tls-h2-static-small        17614     3.53     6.02     7.41        1        0   29.21       42
nginx   static-small               85034     0.69     1.22     1.94        1        0   36.22       53
nginx   static-1m                  22601     2.55     5.13      7.5        1        0   36.12       53
nginx   respond                   244371     0.19     0.66     1.35        1        0   34.19       53
nginx   proxy                      84059     0.66     1.47     2.86        1        0   31.02       54
nginx   tls-h2-static-small        78673     0.77     1.35     2.21        1        0   36.58       68
caddy   static-small               30916     1.31     6.63    11.78        1        0   31.34       47
caddy   static-1m                  13717     3.16    14.04    21.78        1        0   31.35       47
caddy   respond                    58857     0.68     3.34     6.96        1        0   31.83       47
caddy   proxy                      26671     1.87     6.19     9.18        1        0   32.69       51
caddy   tls-h2-static-small        23119     2.15     7.16    11.47        1        0   33.47       51

Findings

  1. Static files are the main gap (scb ≈ 0.24× nginx for 13 B files, ≈ 0.1× for 1 MiB). scb uses only ~28 of 40 available CPU-seconds, i.e. it is latency-bound rather than CPU-bound. Hypothesis: every request does a spawn_blocking round trip for resolve/open, and tokio::fs::File performs one blocking-pool hop per 64 KiB read; nginx uses sendfile and an open-file cache. To verify with perf before changing anything (handoff §14). Candidate fixes, in order: a bounded open-file / metadata cache keyed by canonical path, reading small files in one blocking call, sendfile/splice for plain-TCP large files on Linux.
  2. Pure overhead (respond): scb ≈ 0.39× nginx. Per-request work worth profiling: two label-hash lookups into prometheus-client families, request-id formatting, several Arc<str> allocations, response body boxing/wrapping layers.
  3. Proxy: scb ≈ 0.58× nginx, ≈ 1.8× Caddy 2.6.
  4. TLS + HTTP/2 static inherits finding 1.
  5. Memory: scb peak RSS 42 MiB vs nginx 53–68 MiB vs Caddy 47–51 MiB under this load.
  6. During the first run, oha’s HTTP/2 requests with an explicit Host that differed from :authority were answered 400 by both scb and nginx (RFC 9113 §8.3.1) — the harness now sends no explicit Host header.