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 inst2 insteffvs 1×2 loops3 insteffvs 1×3 loops
respond (64)50.1k100.2k1.001.03×151.0k1.001.03×
respond (256)49.7k99.5k1.001.03×149.7k1.001.03×
static-small (64)38.0k75.3k0.991.05×112.4k0.991.04×
static-small (256)37.8k74.3k0.981.04×111.4k0.981.04×
static-1m (64)5.25k8.82k0.841.00×10.81k0.691.00×
cache-hit (64)40.8k80.8k0.991.05×121.7k1.001.05×
TLS+H2 (256)30.5k62.0k1.021.03×94.9k1.041.02×
proxy (256)21.6k42.6k0.991.04×63.6k0.981.03×
php (64)11.8k11.8k0.501.00×11.8k0.331.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-1m is 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:

Pathconnsreq/sp99 mserrorsinstancesRSS MiB
respond849.6k0.170132
respond3250.7k0.660262
respond6499.5k0.750395
respond128149.4k0.980397
respond256149.2k2.0003102
respond512147.4k5.0103108
respond1024145.3k10.1403121
static-small883.0k0.120395
static-small32111.8k0.350395
static-small64112.0k0.740396
static-small128111.5k1.380396
static-small256110.9k2.6203105
static-small512110.6k5.2903105
static-small1024108.9k10.0503121

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.