Benchmarks
Bare metal (2× Xeon E5-2650 v2) and profile-guided builds — 2026-10-10
First measurements away from the noisy VM: 2× Xeon E5-2650 v2 (Ivy Bridge EP, 8C/16T per
socket, no AVX2), 121 GB, Ubuntu 26.04, kernel 7.0, performance governor, turbo off.
Server on CPUs 0-3 (four physical cores of socket 0, HT siblings idle), upstream and
application processes on 4-7, oha on all of socket 1 (8-15, 24-31). MODE=quick,
3 runs; spread is about 1 % (the VM’s was 5–10 %). scalws cur = commit 4415c31 with both
experimental settings, hyper = same binary with defaults.
Without PGO
| scenario | cur | hyper | nginx 1.28 | nginx 1.31 | cur / 1.31 |
|---|---|---|---|---|---|
| static-small | 81 682 | 74 963 | 94 021 | 96 213 | 0.85 |
| static-1m | 8 790 | 8 765 | 8 821 | 8 831 | 1.00 |
| respond | 140 186 | 121 065 | 199 690 | 210 544 | 0.67 |
| proxy | 63 717 | 50 178 | 76 130 | 89 706 | 0.71 |
| cache-hit | 105 704 | 93 581 | 80 649 | 82 826 | 1.28 |
| tls-h2-static-small | 60 418 | 60 901 | 64 217 | 68 388 | 0.88 |
| php | 28 373 | 27 889 | 16 481 | 16 463 | 1.72 |
Both servers saturate their four cores, so throughput is CPU per request. perf stat
per request (respond): scalws 32.3k user cycles for 17.1k instructions (IPC 0.53) and
43.3k kernel cycles; nginx 14.3k user cycles for 9.9k instructions (IPC 0.69) and 34.9k
kernel cycles. The kernel executes the same number of instructions for both (15.4k) but
needs 24 % more cycles for scalws. Misses per request: L1i 2 610 vs 1 955, L1d 1 112 vs
674, branch 124 vs 88. The gap is mostly code and data footprint (instruction cache),
which also slows the kernel’s share, rather than instruction count.
Profile-guided build
-Cprofile-generate, one quick run of the seven scenarios with both variants as
training, llvm-profdata merge, -Cprofile-use. The training load is the benchmark
load itself (the same request paths a real deployment serves, but a separate training
set is still to be defined).
| scenario | pgo (experimental) | pgo-hyper | nginx 1.31 | pgo / 1.31 |
|---|---|---|---|---|
| static-small | 105 962 | 101 563 | 95 272 | 1.11 |
| static-1m | 8 827 | 8 853 | 8 871 | 1.00 |
| respond | 192 817 | 170 723 | 211 114 | 0.91 |
| proxy | 85 531 | 72 926 | 86 564 | 0.99 |
| cache-hit | 157 259 | 148 319 | 84 877 | 1.85 |
| tls-h2-static-small | 91 135 (CV 10 %) | 88 577 | 66 954 | 1.36 |
| php | 31 273 | 33 753 | 17 053 | 1.83 |
perf stat with PGO (respond): 18.0k user cycles (IPC 0.78), 37.4k kernel cycles —
user cycles -44 %, kernel cycles -14 %. static-1m is bandwidth-bound for both.
Results: xeon:[private host path], xeon:[private host path].
tools/pgo-build.sh with BOLT (own training load, ADR-0047)
Built with PGO_BOLT=1 from commit 9eaebe7; the training load is the script’s own, not
the benchmark. All scenarios ran without errors on the BOLT binary.
| scenario | final (experimental) | final-hyper | nginx 1.28 | nginx 1.31 | final / 1.31 |
|---|---|---|---|---|---|
| static-small | 105 757 | 102 957 | 93 584 | 94 416 | 1.12 |
| static-1m | 9 828 | 9 786 | 9 850 | 9 838 | 1.00 |
| respond | 199 953 | 181 433 | 192 272 | 208 590 | 0.96 |
| proxy | 93 946 | 76 535 | 76 007 | 86 043 | 1.09 |
| cache-hit | 156 901 | 149 413 | 80 287 | 82 996 | 1.89 |
| tls-h2-static-small | 93 076 | 91 485 | 65 025 | 66 968 | 1.39 |
| php | 31 262 | 31 371 | 16 400 | 16 518 | 1.89 |
Results: xeon:[private host path].