<p>For months I'd been saying Askr was "the most efficient way to run Laravel." It's the kind of claim that feels true when you watch a request skip the entire FastCGI relay and land straight in a warm, in-process PHP worker. But feels true and is true are different countries, and the border between them is a benchmark.</p><p>So I finally sat down, spun up a container, and did the thing you're supposed to do before you make a claim: I measured it. What came out taught me two lessons — one about honesty, one about the roadmap — and both were better than the number I went in hoping for.</p><h2>The rules of a fair fight</h2><p>An unfair benchmark is worse than no benchmark, so before touching <code>wrk</code> I nailed down the controls. Same Laravel 13.18.1 app. Same PHP 8.5.8 for everyone (FrankenPHP bundles it; FPM and RoadRunner got it from the ondrej PPA; Askr's <code>libphp</code> is 8.5.8). Same opcache + JIT. Same 4 workers. And crucially, CPU isolation:</p><pre><code class="language-bash"># server pinned to cores 0-3, the load generator to 4-7 — they never fight
taskset -c 0-3  &lt;server&gt; ...
taskset -c 4-7  wrk -t4 -c64 -d20s --latency http://127.0.0.1:8000/bench
</code></pre><p>Five contenders, run sequentially so nobody stole cycles from anyone else:</p><ul><li><p><strong>Askr — worker</strong> (embedded PHP, one process per core, NTS)</p></li><li><p><strong>Askr — CoW</strong> (workers forked copy-on-write from a warm template)</p></li><li><p><strong>Octane + FrankenPHP</strong> (embedded PHP, worker threads, ZTS — the closest peer)</p></li><li><p><strong>Octane + RoadRunner</strong> (Go supervisor + PHP worker processes)</p></li><li><p><strong>PHP-FPM + nginx</strong> (classic FastCGI, boot-per-request)</p></li></ul><h2>The plot twist: the benchmark that lied to me</h2><p>My first run was a disaster dressed as a triumph. FrankenPHP came in at 75 requests per second. Seventy-five! A well-engineered server that should do thousands. And Askr? Askr reported a glorious, chest-thumping 279,000 req/s.</p><p>I did not celebrate. A 30× lead over FrankenPHP isn't a result — it's a red flag that I misconfigured something. So I validated what the servers actually returned, and there it was:</p><pre><code class="language-text">$ curl -s http://127.0.0.1:8000/bench
askr: php worker unavailable          # HTTP 502
</code></pre><p>Askr's "279k req/s" was 2.4 million HTTP 502s, which <code>wrk</code> had happily counted as "requests." (One had <code>ASKR_APP_BASE</code> unset; the worker booted but couldn't find the app.) Meanwhile FrankenPHP's 75 req/s was real — and that was the more interesting bug.</p><p>The <code>/bench</code> route lived under Laravel's web middleware, which runs <code>StartSession</code>. <code>wrk</code> sends no cookies, so every single request created a brand-new session — and the default <code>SESSION_DRIVER</code> is database. Which means every request did:</p><pre><code class="language-sql">INSERT INTO sessions ...   -- into SQLite, which has ONE writer lock
</code></pre><p>Sixty-four concurrent connections, all fighting over SQLite's single-writer lock. It wasn't measuring the server at all — it was measuring a database write-lock, and it flattened everyone.</p><p>The fix was two lines and a lesson:</p><pre><code class="language-bash">SESSION_DRIVER=array        # no session persistence — same for every stack
# ...and validate every run: 200 + expected body + ZERO non-2xx, or throw it out
</code></pre><p>A req/s figure without response validation is noise. I'd nearly published noise. Write that on the wall.</p><h2>The real numbers</h2><p>With the confound gone and every run validated, here's <code>/bench</code> — a small JSON response that isolates server + framework overhead (median of 3 runs):</p><p>Stack req/s p50 p99 Askr — CoW 18,207 3.6 ms 11 ms Askr — worker 18,132 3.1 ms 14 ms Octane + FrankenPHP 8,563 7.3 ms 11 ms PHP-FPM + nginx 4,380 14.4 ms 17 ms Octane + RoadRunner 3,861 12.9 ms ~100 ms</p><p>So on raw server overhead, Askr lands at roughly 2.1× FrankenPHP (the closest embedded-PHP peer), ~4× PHP-FPM, and ~4.7× RoadRunner.</p><p>But I promised honesty, so here's the counterpoint. Swap <code>/bench</code> for <code>/bench-db</code> — a real <code>SELECT count(*)</code> — and the field compresses:</p><p>Stack req/s Askr — CoW 13,424 Askr — worker 8,973 Octane + FrankenPHP 7,249 PHP-FPM + nginx 3,868 Octane + RoadRunner 3,307</p><p>Askr still leads, but the gap narrows — because now the query is doing the work, and the server matters less. That's the honest shape of it: Askr's advantage is biggest where framework and boot overhead dominate, and shrinks as your app does more real work per request. Anyone who tells you their server is 4× faster for your app is selling you a microbenchmark.</p><h2>The metric that rewrote the roadmap</h2><p>Here's where the benchmark earned its keep. My next planned feature was a per-core <code>io_uring</code> I/O core — weeks of Rust, a whole runtime rewrite, "the biggest efficiency step." Before building it, I asked the data one question: is I/O even the bottleneck?</p><p>Askr exposes its own timing, so during a 20-second load I just read the meter:</p><pre><code class="language-text">askr_php_seconds_total      1261.2
askr_request_seconds_total  1267.5
</code></pre><p>Do the division. PHP execution is 99.5% of request time. Everything else — accept, read, write, routing, the entire Rust layer — is 0.5%.</p><p><code>io_uring</code> optimizes exactly that 0.5%. A multi-week rewrite to shave a sliver off half a percent, while the PHP engine sits at 99.5% completely untouched. The benchmark didn't just validate a claim — it talked me out of the wrong project. <code>io_uring</code> is now deprioritized, in writing, with a reason. The levers that actually matter — boot once, opcache + JIT, no FastCGI hop — are the ones Askr already pulls.</p><p>That, to me, is the whole point of measuring. Not the bragging rights. The course correction.</p><h2>Why Askr comes out ahead</h2><p>The architecture explains the gap without any magic:</p><ul><li><p><strong>No per-request boot.</strong> Like Octane and FrankenPHP, the app boots once. Unlike FPM, there's no framework bootstrap on every request.</p></li><li><p><strong>No FastCGI hop.</strong> PHP runs in-process — no socket serialization between a web server and a PHP pool.</p></li><li><p><strong>Processes, not threads.</strong> This is the real difference from FrankenPHP: Askr runs non-ZTS PHP, one process per core, so there's no thread-safety locking on the hot path. The OS scheduler does the work. That's most of the ~2×.</p></li><li><p><strong>Copy-on-write</strong> shares a warm template across workers — competitive throughput at slightly lower memory.</p></li></ul><h2>The caveats, because I'd want them</h2><ul><li><p><strong>Shared dev VM.</strong> Run-to-run variance was real (one Askr-CoW sample came in ~20% low; the tables use medians). Read these as ratios and shapes, not lab-grade absolutes.</p></li><li><p><strong>RSS is summed resident memory,</strong> which overcounts shared pages — it flatters the one-process thread model (FrankenPHP) and penalizes the many-process ones (Askr, FPM, RoadRunner). PSS would be fairer; I didn't measure it.</p></li><li><p><strong>RoadRunner ran on Octane's defaults.</strong> Its ~100 ms p99 says there's tuning I didn't chase. Don't read its number as RoadRunner's best.</p></li><li><p><strong>Small payloads, arm64, 4 workers, session=array.</strong> This is server + framework overhead, not database throughput or large-response streaming.</p></li></ul><p>The whole harness — including the confound and the validation — is in <code>docs/BENCHMARKS.md</code>. Run it yourself; that's the only benchmark worth trusting.</p><h2>The takeaway</h2><p>Two surprises, then. The first: the claim held — Askr really is about twice the closest in-process peer and four times FastCGI on server overhead, and the numbers are honest because I threw out the ones that weren't.</p><p>The second, and the better one: measuring told me not to build the thing I was about to build. The bottleneck was never I/O. It was always PHP — which is exactly the thing Askr already makes as cheap as it can be.</p><p>Measure before you optimize. Validate before you brag. And when the data tells you your next feature is a waste of time, thank it and go build something that isn't.</p><p>Now — HTTP/3, anyone? 🌳</p>