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
| Workload | Superweb | nginx 1.31.6 | Caddy 2.11.7 | lighttpd 1.4.85 |
| 14 B text, identity | 190,812 | 154,169 | 58,770 | 181,068 |
| 122,161 B JavaScript, identity | 47,117 | 46,802 | 41,505 | 44,411 |
| JavaScript, cached gzip (29,974 B) | 116,156 | 110,414 | 41,768 | 104,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):
| Workload | vs nginx | vs Caddy | vs lighttpd |
| 14 B text, identity | 1.238× | 3.247× | 1.054× |
| 122,161 B JavaScript, identity | 1.007× | 1.135× | 1.061× |
| JavaScript, cached gzip (29,974 B) | 1.052× | 2.781× | 1.115× |
| Workload | Fastest competitor by median | Superweb throughput difference | Rounds ahead of that competitor |
| 14 B text, identity | lighttpd | +5.38% | 5/5 |
| 122,161 B JavaScript, identity | nginx | +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
| Workload | Earlier cached build | Current build | Throughput change |
| 14 B text, identity | 243,351 | 190,812 | -21.6% |
| 122,161 B JavaScript, identity | 44,776 | 47,117 | +5.2% |
| JavaScript, cached gzip (29,974 B) | 123,943 | 116,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
| Workload | Server | Min–max requests/s | Median run p50 | Median run p99 |
| 14 B text, identity | superweb | 188,458–193,081 | 0.460 ms | 0.622 ms |
| 14 B text, identity | nginx | 153,278–155,457 | 0.556 ms | 0.950 ms |
| 14 B text, identity | caddy | 58,189–59,080 | 1.600 ms | 4.320 ms |
| 14 B text, identity | lighttpd | 180,814–182,956 | 0.424 ms | 0.980 ms |
| 14 B text, identity | superweb-before | 240,698–244,435 | 0.385 ms | 0.480 ms |
| 122,161 B JavaScript, identity | superweb | 46,966–47,736 | 1.080 ms | 1.460 ms |
| 122,161 B JavaScript, identity | nginx | 46,182–46,995 | 1.080 ms | 1.490 ms |
| 122,161 B JavaScript, identity | caddy | 41,141–41,597 | 2.230 ms | 5.940 ms |
| 122,161 B JavaScript, identity | lighttpd | 44,001–44,668 | 1.140 ms | 1.580 ms |
| 122,161 B JavaScript, identity | superweb-before | 44,722–45,307 | 1.140 ms | 1.500 ms |
| JavaScript, cached gzip (29,974 B) | superweb | 115,269–120,508 | 0.436 ms | 0.600 ms |
| JavaScript, cached gzip (29,974 B) | nginx | 108,329–112,381 | 0.469 ms | 0.692 ms |
| JavaScript, cached gzip (29,974 B) | caddy | 41,421–42,182 | 2.250 ms | 5.880 ms |
| JavaScript, cached gzip (29,974 B) | lighttpd | 102,761–106,407 | 0.530 ms | 1.310 ms |
| JavaScript, cached gzip (29,974 B) | superweb-before | 121,586–125,589 | 0.507 ms | 0.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.