Skip to content

Results

Measured numbers by release. Each was taken on the built release image (or, where noted, a release-candidate build), 100% 2xx verified.

v0.4.0 → v0.4.1

The v0.4.1 headline was database latency. Measured on the release image; the v0.4.0 control reproduced the lab’s recorded baselines, so the comparison is apples-to-apples.

Workloadv0.4.0v0.4.1Change
Single-node SQLite point-SELECT p5044.010 ms0.211 ms208×
Single-node SQLite INSERT p501.07 ms0.267 ms
db.php (10 queries) per request p50~444 ms~4.4 ms101×
sha256 (per digest)306 ns133 ns2.3×
cpu.php c=16 RPS78.7147.91.88× (flips a loss vs php-fpm to a win)
hello.php c=16 RPS730781+7%

Where the DB number came from: php’s mysqlnd client does not set TCP_NODELAY; the litewire MySQL frontend wrote each result-set packet separately. The two together produced a Nagle + delayed-ACK deadlock on every multi-packet response — a fixed ~44 ms stall. Coalescing the result-set into a single write removed it. INSERTs (single OK packet) were never affected, which is exactly why SELECT was slow and INSERT was fast — the diagnostic fingerprint.

Where the sha256 number came from: a C++-only compiler flag in the SDK build silently disabled the compiler’s function-attribute detection, which disabled the SHA-NI code path. Restoring it roughly halved sha256 cost and flipped cpu.php from a ~2× loss to php-fpm into a win.

Against php-fpm on the local runtimes suite, v0.4.1 wins every category measured: cpu (was the clearest loss), database (by construction), and small-script throughput.

v0.4.2 (in progress)

Measured on a v0.4.2-dev image (wave-1 changes + the HTTP TCP_NODELAY fix) vs published v0.4.1, --cpus 1.

Cellv0.4.1v0.4.2-devChange
hello c=1 p50 (latency-bound)1.79 ms1.64 ms−8.6%
hello c=16 RPS (throughput)842895+6.3%
hello c=16 p9929.3 ms25.4 ms−13%
cpu c=16 RPS559570+2%

The −13% p99 and −8.6% c=1 p50 are the TCP_NODELAY signature (tail and single-request latency); the modest RPS gain is the combined effect of that plus wave-1. Worker-dispatch and further items are still being measured — see Findings for what the data ruled in and out.

v0.5.0 — resource-aware autotuning (in progress)

v0.5.0’s headline is container-derived PHP tuning: on boot in serve mode, ePHPm reads the cgroup CPU and memory limits and derives an opcache / memory / realpath / assertions profile, with opcache.validate_timestamps off (deploys become events via ephpm deploy / ephpm cache reset). Operator config overrides any derived value.

Setup (reproducible): a 300-file require_once app (index.php includes 300 tiny class files each request — deliberately stat-heavy), --cpus 1 --memory 512m, oha at c=16, 15 s, warmup first, 100% 2xx. v0.4.2 runs stock PHP ini; v0.5.0 auto-derives.

The profile v0.5.0 derived for this box (its own startup log):

autotune (serve): cpu_quota=1.00 mem=512MiB (cgroup v2) ->
  workers=1[cgroup_quota] opcache.memory_consumption=92MB memory_limit=356M
  interned=8MB jit_buffer=32MB (buffer-only, jit off) max_files=20000
  realpath=16M/ttl=600 validate_timestamps=0 assertions=-1

(It also logs the deploys-are-events contract, and — because this bench config left the RESP listener disabled — correctly WARNs that ephpm deploy can’t reach the server.)

Result:

v0.4.2 (stock ini)v0.5.0 (autotuned)Change
RPS8741144+31%
p5015.0 ms14.8 ms−1%
p9920.6 ms19.2 ms−7%

+31% throughput with zero operator tuning, driven mainly by validate_timestamps=0 eliminating ~300 stat() syscalls per request on this include-heavy workload, plus the realpath cache and compiled-out assertions.

Honest bounds: this app is deliberately near the upper end of what autotuning buys (300 includes/request). A single-file hello.php shows ~0% (it has nothing to stat and fits any opcache). A real framework app lands between — wherever its file count and filesystem cost sit, and higher on container overlay / network filesystems where stat() is pricier. The number is a demonstrated ceiling-ish case, not a promise for every app.

How to read these

  • Absolute numbers are environment-specific. The db.php p50 was measured differently (single-node reproduction) from the raw point-SELECT p50; both are real, both are labeled. RPS ceilings under podman/WSL are RTT-capped and do not transfer to a cluster.
  • Deltas are the durable claim. “208×” and “−13% p99” hold across environments; “895 RPS” does not.
  • php-fpm comparisons use the official php:8.4-fpm image with an opcache+JIT ini overlay, nginx front, same fixtures. The fpm control also reproduces the lab’s recorded fpm numbers.