Internals: benchmark reports
Adaptive Runtime on bare metal: scaling, autoscaling and stability (2026-10-12)
Repeats the measurements of 2026-10-07-adaptive-runtime.md (development VM) on the
bare-metal host of the release-mode runs, with the same scripts (bench/instances.sh,
scripts/controller-d5.sh; CPU sets via UPSTREAM_CPU and LOAD_CPUS).
Host: bare metal, 2× Xeon E5-2650 v2 (Ivy Bridge EP, 8 cores per socket), 121 GB, Ubuntu 26.04, kernel 7.0, tcp_migrate_req=1, performance governor, turbo off.
Topology separate from the release-mode comparisons: instance i on CPU i (one event loop each), upstream and PHP-FPM (4 workers) on CPU 3,
oha on the other socket (CPUs 8-15, 24-31).
scalwsd: the PGO + BOLT build of commit a7dc639 with the default HTTP/1 paths (the
final-hyper binary of run 7); scalws-controller built from the same tree.
D1: N instances (reuseport) vs. one instance with the same CPUs
bench/instances.sh, 3 runs × 10 s after a 3 s warm-up, medians; eff = rps(N) / (rps(1) × N);
“vs 1×N loops” = N instances against one instance with N event loops on the same CPUs.
| Scenario (conns) | 1 inst | 2 inst | eff | vs 1×2 loops | 3 inst | eff | vs 1×3 loops |
|---|---|---|---|---|---|---|---|
| respond (64) | 50.1k | 100.2k | 1.00 | 1.03× | 151.0k | 1.00 | 1.03× |
| respond (256) | 49.7k | 99.5k | 1.00 | 1.03× | 149.7k | 1.00 | 1.03× |
| static-small (64) | 38.0k | 75.3k | 0.99 | 1.05× | 112.4k | 0.99 | 1.04× |
| static-small (256) | 37.8k | 74.3k | 0.98 | 1.04× | 111.4k | 0.98 | 1.04× |
| static-1m (64) | 5.25k | 8.82k | 0.84 | 1.00× | 10.81k | 0.69 | 1.00× |
| cache-hit (64) | 40.8k | 80.8k | 0.99 | 1.05× | 121.7k | 1.00 | 1.05× |
| TLS+H2 (256) | 30.5k | 62.0k | 1.02 | 1.03× | 94.9k | 1.04 | 1.02× |
| proxy (256) | 21.6k | 42.6k | 0.99 | 1.04× | 63.6k | 0.98 | 1.03× |
| php (64) | 11.8k | 11.8k | 0.50 | 1.00× | 11.8k | 0.33 | 1.00× |
- Web-tier paths scale linearly on bare metal (efficiency 0.98–1.04) and N instances are 1.02–1.05× one instance with N event loops on the same CPUs: multi-instance costs nothing here, and gains a little.
static-1mis bandwidth-bound (loopback copy): 10.8k req/s with three instances or one instance with three loops alike.- PHP is bound by PHP-FPM on one CPU: 11.8k req/s with one, two or three instances (efficiency 0.50 and 0.33): the case the classifier must not answer with more scalwsd instances.
- RSS (sum of the instances): 30–54 MiB for one instance (34 MiB on the static paths), 86–112 MiB for three; one instance with three loops: 33–62 MiB.
- Zero non-2xx responses at every point.
D5: autoscaling under load (web-tier mode, min 1, max 3)
Concurrency sweep, 20 s per point, scripts/controller-d5.sh:
| Path | conns | req/s | p99 ms | errors | instances | RSS MiB |
|---|---|---|---|---|---|---|
| respond | 8 | 49.6k | 0.17 | 0 | 1 | 32 |
| respond | 32 | 50.7k | 0.66 | 0 | 2 | 62 |
| respond | 64 | 99.5k | 0.75 | 0 | 3 | 95 |
| respond | 128 | 149.4k | 0.98 | 0 | 3 | 97 |
| respond | 256 | 149.2k | 2.00 | 0 | 3 | 102 |
| respond | 512 | 147.4k | 5.01 | 0 | 3 | 108 |
| respond | 1024 | 145.3k | 10.14 | 0 | 3 | 121 |
| static-small | 8 | 83.0k | 0.12 | 0 | 3 | 95 |
| static-small | 32 | 111.8k | 0.35 | 0 | 3 | 95 |
| static-small | 64 | 112.0k | 0.74 | 0 | 3 | 96 |
| static-small | 128 | 111.5k | 1.38 | 0 | 3 | 96 |
| static-small | 256 | 110.9k | 2.62 | 0 | 3 | 105 |
| static-small | 512 | 110.6k | 5.29 | 0 | 3 | 105 |
| static-small | 1024 | 108.9k | 10.05 | 0 | 3 | 121 |
The static-small sweep followed the respond sweep without an idle pause, so three instances were still active for it.
Bursts from idle to 1024 connections for 5 s (three in a row, 8 s apart): 47.0k → 95.6k → 144.5k req/s while the controller grew 1 → 3 → 3 instances; p99 24 → 18 → 10 ms; zero errors. The instance counts are those at the end of each burst: the second burst started on one instance and ended on three. The pool scaled back to one instance after the sweep.
The other D5 checks passed as on the VM: HTTP/2 with 8 × 32 streams across a scale-in from
three instances to one lost no processed stream (224 requests the client had not sent yet
were cancelled by it after GOAWAY); four slow 1 MiB downloads across a scale-in completed;
a reload while scaling to three left all instances on the new generation with zero
errors; at max_instances the controller logged the reason instead of acting; the memory
guard refused a new instance; 7 autoscaler actions, no restarts.
Compared with a fixed single instance on one CPU (~50k req/s on respond, D1), the autoscaled pool reached ~3× the throughput once three instances were active, on three times the CPUs, as D1 predicts.
Results: bench-results/xeon-r7-instances (results.csv) and xeon-r7-d5.csv on the
benchmark host.