superweb

M5 MAX / LOCAL HTTP/1.1 / PRE-LINUX PORT

Optimized cached static-file comparison — 2026-10-17

Five interleaved rounds compare the final Superweb build against nginx, Caddy, lighttpd, and the earlier already-cached Superweb build. Values below are median requests/second. Close margins must be read with the ranges and round counts; these measurements do not establish a universal fastest server.

Superweb had the highest median in each tested workload: 190,812 requests/s for tiny files, 47,117 for uncompressed JavaScript, and 116,156 for cached gzip. On gzip it led nginx by 5.2%, lighttpd by 11.5%, and reached 2.78× Caddy's throughput. The uncompressed JavaScript lead over nginx was just 0.7%, effectively a tie. Compared with the earlier cached Superweb build, tiny-file and gzip throughput regressed; the before/after table below records those costs.

Raw measurements, binary hashes, commands, and response probes (local paths redacted).

Throughput

WorkloadSuperwebnginx 1.31.6Caddy 2.11.7lighttpd 1.4.85
14 B text, identity190,812154,16958,770181,068
122,161 B JavaScript, identity47,11746,80241,50544,411
JavaScript, cached gzip (29,974 B)116,156110,41441,768104,150

Each measurement lasted three seconds after a separate one-second warmup. All 75 measurements and their warmups completed with zero reported HTTP or socket errors. Responses were checked before and after every measurement for status, encoding, decoded content, and stable wire-body hash.

Superweb's throughput divided by each competitor's throughput (above 1 means Superweb was faster):

Workloadvs nginxvs Caddyvs lighttpd
14 B text, identity1.238×3.247×1.054×
122,161 B JavaScript, identity1.007×1.135×1.061×
JavaScript, cached gzip (29,974 B)1.052×2.781×1.115×
WorkloadFastest competitor by medianSuperweb throughput differenceRounds ahead of that competitor
14 B text, identitylighttpd+5.38%5/5
122,161 B JavaScript, identitynginx+0.67%4/5
JavaScript, cached gzip (29,974 B)nginx+5.20%5/5

The round counts compare measurements in the same round; these are observations, not statistical confidence intervals. A small lead with overlapping ranges is not a reliable general ranking.

Before and after

WorkloadEarlier cached buildCurrent buildThroughput change
14 B text, identity243,351190,812-21.6%
122,161 B JavaScript, identity44,77647,117+5.2%
JavaScript, cached gzip (29,974 B)123,943116,156-6.3%

The earlier build already has the gzip cache. It is the Superweb binary used in the first cached comparison, with SHA-256 ce55ffc291f9989c55e49ce5598c326a6eed4c99826e8b29d22f4ff81d422f56. Its separate macOS SO_REUSEPORT listeners sent all connections to one worker: an instrumented check recorded 2,901,567 requests on worker 5 and zero on the other five. The current shared listener distributes connections across workers. That fixes real parallelism and exclusive binding, but changes batching and scheduling on this loopback workload and can reduce throughput for tiny or compressed responses. The before/after table includes that tradeoff.

The historical 193× gzip-cache improvement was measured against a different, pre-cache baseline in the first report. It is not the speedup from the optimizations in this report.

Variation and latency

WorkloadServerMin–max requests/sMedian run p50Median run p99
14 B text, identitysuperweb188,458–193,0810.460 ms0.622 ms
14 B text, identitynginx153,278–155,4570.556 ms0.950 ms
14 B text, identitycaddy58,189–59,0801.600 ms4.320 ms
14 B text, identitylighttpd180,814–182,9560.424 ms0.980 ms
14 B text, identitysuperweb-before240,698–244,4350.385 ms0.480 ms
122,161 B JavaScript, identitysuperweb46,966–47,7361.080 ms1.460 ms
122,161 B JavaScript, identitynginx46,182–46,9951.080 ms1.490 ms
122,161 B JavaScript, identitycaddy41,141–41,5972.230 ms5.940 ms
122,161 B JavaScript, identitylighttpd44,001–44,6681.140 ms1.580 ms
122,161 B JavaScript, identitysuperweb-before44,722–45,3071.140 ms1.500 ms
JavaScript, cached gzip (29,974 B)superweb115,269–120,5080.436 ms0.600 ms
JavaScript, cached gzip (29,974 B)nginx108,329–112,3810.469 ms0.692 ms
JavaScript, cached gzip (29,974 B)caddy41,421–42,1822.250 ms5.880 ms
JavaScript, cached gzip (29,974 B)lighttpd102,761–106,4070.530 ms1.310 ms
JavaScript, cached gzip (29,974 B)superweb-before121,586–125,5890.507 ms0.890 ms

Latency columns are medians of each run's reported percentiles, not pooled percentiles. wrk's closed-loop latency measurements are not an open-loop tail-latency test.

What changed

• Cache gzip bytes and their SHA-256 once per file version and worker, shared by HTTP/1.1 and HTTP/2. Original bodies, compressed bodies, and complete HTTP/1 response copies share a 32 MiB budget per worker. Active sends pin their version through replacement, deletion, and eviction pressure.

• Share one listening socket and accept queue between workers on macOS, including TLS. A second server cannot take over the same address. SW_WORKER_STATS=1 exposes per-worker acceptance and HTTP/1 request counts.

• Cache common HTTP/1.1 response-header templates per file version and encoding, patching Date on every response. Two templates per slot cost at most another 1 MiB per worker. HEAD, HTTP/1.0, closing connections, ranges, and invalidation retain their semantics.

• Parse common requests once using stack-local structures. Preserve and reparse headers only when draining a request body across reads. Skip request-line formatting when logging is disabled.

• Use kqueue's unread-byte count to avoid redundant plain-TCP reads, retaining read notifications during write backpressure and honoring half-close. TLS continues to use OpenSSL's buffered-read behavior.

• Cache complete HTTP/1.1 header-and-body responses when the shared 32 MiB budget permits. These use a contiguous write and skip the per-connection header copy. Update the cached Date only when no other response pins the entry; otherwise use a fresh connection header with the original cached body. Complete-response copies consume cache capacity, so they can reduce the number of distinct bodies retained. A three-round exploration measured +1.5% identity throughput and -0.3% gzip throughput, both small effects; do not attribute the final session's absolute speed changes to this feature alone. Exploration data.

• Copy a small body into the remaining response-header buffer and use a contiguous write; use writev for separate larger memory bodies. Header-only templates never contain body bytes.

• Improve the ARM64 assembly scanner's no-match NEON path, bounded four-byte terminator checks, and literal GET/HEAD recognition. Five alternating microbenchmark rounds measured 1.24–1.79× parser throughput, depending on the case. These are parser timings, not end-to-end HTTP speedups. Raw microbenchmarks and differential-test driver.

• Enable -flto for the normal server build, allowing Clang to optimize across translation units. A separate two-round exploration selected it before this validation run; scheduling-priority variants were discarded. Compiler exploration.

The complete five-round run before enabling LTO is also preserved: stage-one measurements. Its medians were 183,929 / 42,672 / 108,295 requests/s for Superweb; nginx's JavaScript medians were 42,739 / 108,841. It showed a near tie rather than an across-the-board win. The subsequent LTO-only five-round run is preserved too: Superweb measured 185,161 / 42,879 / 108,122 requests/s, and nginx 149,684 / 42,398 / 108,528. It still showed a gzip near tie. Results from these sessions are not pooled.

Conditions and limits

• Apple M5 Max, arm64 Darwin 25.6.0, 128 GiB RAM, six performance and twelve efficiency cores. Apple clang 21, -O3 -mcpu=native -flto for the final native server. Started 2026-10-17T12:58:52-0500; finished 2026-10-17T13:04:12-0500.

• wrk 4.2.0, six client threads, 96 persistent HTTP/1.1 connections, one outstanding request per connection. Client and server share this machine over loopback. No TLS in the timed comparison.

• Superweb, nginx, and lighttpd configured for six workers; Caddy uses GOMAXPROCS=6. This is configuration parity, without CPU affinity or equal scheduling guarantees. The earlier Superweb listener bug is described above.

• One benchmark server at a time, a shared temporary localhost port, and rotated server/workload order. Other applications stayed running. One-minute system load averages ranged from 7.67 to 12.02; this was not an isolated host. Caffeinate kept the machine awake during measurements, and timing checks reject suspend-stretched runs.

• Access logging disabled. Superweb revalidation 1,000 ms; nginx open-file cache validity one second; lighttpd simple stat cache. Superweb caches gzip in RAM on demand. nginx uses gzip_static and Caddy uses precompressed gzip sidecars. lighttpd uses mod_deflate with a disk cache and the supported "gzip.level" => 6 option. All gzip bodies are 29,974 bytes and decode to the same fixture. Server headers and implementation details differ.

• Fixture: Startup Park sim.js, 122,161 bytes, SHA-256 ac7fa7483070901815e40ebc70cec9de9538a1c83bfe25ad43708bb27e23f474. The source file is copied, never modified. The tiny fixture is Hello, World! plus newline, 14 bytes.

• These runs do not establish cold-cache, large-file, broad-working-set, remote-network, TLS, HTTP/2 throughput, proxy, CPU-efficiency, or memory-efficiency rankings. Cache and TLS/HTTP/2 behavior are separately covered by functional tests.

Validation and reproduction

make test, make asan (AddressSanitizer and UndefinedBehaviorSanitizer), and make x64 passed. Coverage includes cache replacement/deletion, byte-budget and slot exhaustion, slow responses spanning replacement, an extra request arriving while writes are blocked, gzip/identity/HEAD/Range/304, current Date with cached headers, TLS and HTTP/2, fragmented headers, 400 pipelined requests beyond the input-buffer size, half-close, worker distribution, and exclusive binding. ARM64 assembly additionally passed 500,000 differential cases against the prior implementation and guard-page tests for short/truncated inputs; assembly is not instrumented by ASan. The x64 check is a cross-build plus Rosetta parser tests.

brew install nginx caddy lighttpd wrk
make superweb
python3 test/benchmark_servers.py \
  --asset /path/to/sim.js \
  --output build/benchmark-rerun \
  --rounds 5 --seconds 3 --warmup 1 \
  --workers 6 --threads 6 --connections 96

For the before/after rows, add --baseline /path/to/earlier-cached-superweb --baseline-all-cases. The earlier cached source diff is in the first report's raw JSON. The output directory must not already contain results.json. To compare without LTO, rebuild with make -B superweb SERVER_OPT_FLAGS=. The harness starts and stops only its own foreground process groups; it leaves existing development servers running.

Download raw measurements (local paths redacted)