Under construction

This documentation is still in progress. Some information may be incomplete or out of date. Please check back later.

Docs/Performance

Running it

Performance

httpjet is fast — and these docs are strict about where the time goes. Every figure names its regime, because a loopback benchmark (synthetic, on-box, PGO build) is not the same thing as production telemetry (real CDN-fronted traffic). The short version: on a Cloudflare-fronted PHP site, almost all wall time is PHP, and httpjet's own CPU is a rounding error.

~908k rps
h2 static, c=50, dedicated-host loopback
44.8k rps
PHP/LSAPI over HTTP/1.1, PGO loopback
101×
cache hit vs the same page through PHP
0.2%
httpjet's share of production request latency
1.55IPC
h2 hit path at saturation (hardware counters)
4,852rps
1 MiB plaintext sendfile, c=50

Read the regime, not just the number

loopback benchmark · synthetic, on-box, PGO production telemetry · real CF-fronted traffic hardware counters · perf at saturation

A loopback benchmark removes the network, real TLS handshakes and the CDN — it isolates the server's code path, which is exactly what you want to compare two servers and exactly what you must not generalize to end-user latency. Production telemetry measures what users feel, dominated by the backend and the edge. The same server shows ~908k req/s on loopback and ~0.2% of user-visible latency in production; those answer different questions.

Loopback throughput

The primary dataset is the August 2026 dedicated-host three-way (httpjet vs OpenLiteSpeed vs nginx on a quiet box, all release/PGO-equivalent builds). The June PGO campaign against a commercial-LiteSpeed baseline remains the PHP-path reference.

HTTP/2 static throughput by concurrency

dedicated-host loopback · 2026-08-27 · empty bodies · req/s

250 k rps500 k rps750 k rps1,000 k rpsc=1 (1 KB)httpjet · c=1 (1 KB): 58 k rps58OpenLiteSpeed · c=1 (1 KB): 50 k rps50— not completedc=50httpjet · c=50: 908 k rps908OpenLiteSpeed · c=50: 793 k rps793— not completedc=200httpjet · c=200: 937 k rps937OpenLiteSpeed · c=200: 687 k rps687— not completedc=1000httpjet · c=1000: 860 k rps860OpenLiteSpeed · c=1000: 878 k rps878— not completed
httpjetOpenLiteSpeednginx

nginx could not complete the low-concurrency h2 cells in this harness (blank). Source: bench-results/versus5-dedicated-final-20260827.csv

table view
httpjetOpenLiteSpeednginx
c=1 (1 KB)58 k rps50 k rps—
c=50908 k rps793 k rps—
c=200937 k rps687 k rps—
c=1000860 k rps878 k rps—

One h2 connection is not a ceiling (~58k req/s at c=1), and throughput keeps rising with concurrency instead of collapsing; the earlier "h2 collapses past c=50" shape belonged to the June build and its measurement conditions, not the transport. HTTP/1.1 empty bodies at c=1000: ~202k vs OpenLiteSpeed's ~183k.

Heavy .htaccess static (806-line chain, /robots.txt)

PGO three-way · 2026-08-28 · c=100 · req/s

100 k rps200 k rps300 k rps400 k rpsHTTP/1.1httpjet · HTTP/1.1: 168.7 k rps168.7OpenLiteSpeed · HTTP/1.1: 169.1 k rps169.1nginx · HTTP/1.1: 145.6 k rps145.6HTTP/2httpjet · HTTP/2: 298 k rps298OpenLiteSpeed · HTTP/2: 283.3 k rps283.3nginx · HTTP/2: 125.4 k rps125.4
httpjetOpenLiteSpeednginx

httpjet runs the full .htaccess pipeline on every static request; the default-on fast-path memo is what closes the gap.

table view
httpjetOpenLiteSpeednginx
HTTP/1.1168.7 k rps169.1 k rps145.6 k rps
HTTP/2298 k rps283.3 k rps125.4 k rps

Fast-path memo, same binary A/B

forum chain · /robots.txt · c=100 · req/s

100 k rps200 k rps300 k rps400 k rpsHTTP/1.1memo off · HTTP/1.1: 107 k rps107memo on · HTTP/1.1: 153 k rps153HTTP/2memo off · HTTP/2: 137 k rps137memo on · HTTP/2: 248 k rps248
memo offmemo on

+43% h1, +81% h2. Byte-identity gate: scripts/fast_memo_parity_test.sh (192 cells, zero differences).

table view
memo offmemo on
HTTP/1.1107 k rps153 k rps
HTTP/2137 k rps248 k rps

PHP over LSAPI, HTTP/1.1

loopback · PGO build · 2026-06 · req/s

20 k rps40 k rps60 k rps80 k rpsPHP hello-worldhttpjet (c=40) · PHP hello-world: 44.8 k rps44.8LiteSpeed (c=40) · PHP hello-world: 38.5 k rps38.5LiteSpeed best (c=60) · PHP hello-world: 41.2 k rps41.2
httpjet (c=40)LiteSpeed (c=40)LiteSpeed best (c=60)

Same hardware, same lsphp binary; LiteSpeed needed higher concurrency to reach its best.

table view
httpjet (c=40)LiteSpeed (c=40)LiteSpeed best (c=60)
PHP hello-world44.8 k rps38.5 k rps41.2 k rps

The origin cache result

The single largest performance fact about the real workload is not a micro-optimization — it is the origin page cache. For the cacheable fraction of traffic, a hit replaces an entire PHP render with a memory (or tmpfs) copy.

One forum page: cache hit vs full PHP render

HTTPS loopback · PGO · 152 KB page · req/s

5,000 rps10,000 rps15,000 rps20,000 rpspage-cache HIT (p50 2 ms)requests / s · page-cache HIT (p50 2 ms): 19,912 rps19,912full DB-backed PHP render (p50 93 ms)requests / s · full DB-backed PHP render (p50 93 ms): 196 rps196

≈101× the throughput, ≈46× lower latency. The cache removes the backend from the request entirely.

table view
requests / s
page-cache HIT (p50 2 ms)19,912 rps
full DB-backed PHP render (p50 93 ms)196 rps

A 2026-08-30 refresh under shared-box conditions (live production on the same host — directional only) put cache-hit serves at ~5.4k req/s h2 and ~6.7k h1 on real /help/* pages. The production hit ratio reads low for a structural reason: ~51% of origin GET-200s are one-shot bot crawls whose repeat interval exceeds any TTL, so no origin tier can absorb them — the lever there is edge TTL.

Where the time actually goes

Where a production request spends its time

live sample · 2026-08-30 · mean request 9.99 ms

2.5 ms5 ms7.5 ms10 msLSAPI TTFB inside PHPmilliseconds · LSAPI TTFB inside PHP: 8.96 ms8.96everything else (TLS, h2, rewrite 5.4 µs, cache lookup 3.2 µs, log)milliseconds · everything else (TLS, h2, rewrite 5.4 µs, cache lookup 3.2 µs, log): 1.03 ms1.03

httpjet-side CPU is ~0.2% of user-visible latency; the levers are PHP speed and the cache hit ratio.

table view
milliseconds
LSAPI TTFB inside PHP8.96 ms
everything else (TLS, h2, rewrite 5.4 µs, cache lookup 3.2 µs, log)1.03 ms
⎁
Measured

Across millions of production requests, the lsphp render is 86.5–98.8% of request wall time. httpjet's own CPU is about 0.2% — roughly 138 µs per request, ~38 µs per cache hit, and that includes TLS, HTTP/2, rewrite, cache lookup and access logging. The 2026-08-30 sample: mean request 9.99 ms, 8.96 ms of it LSAPI TTFB, rewrite 5.4 µs, cache lookup 3.2 µs; pool acquire p99.9 < 1 ms.

production telemetry · --telemetry-file + --php-slow-log

If httpjet is 0.2% of latency, halving its CPU shaves ~0.1% off a page — imperceptible. Turning a PHP render into a cache hit removes the other ~99%. That asymmetry is why the project stopped shaving the serving path and invested in the cache, PHP/opcache tuning and raising the cacheable fraction.

At the instruction floor

⎁
Measured

At saturation the h2 cache-hit path shows IPC 1.55, a 0.31% branch-miss rate and 1.6 L1d misses per 100 instructions: execution is efficient and the bottleneck is instructions retired per request, not locality. Futex traffic (~85% of syscall time) turned out to be idle parking and cross-thread waker churn — the box was ~50% idle at the apparent ceiling — not lock contention.

perf stat / strace · off-CPU analysis

Two structural moves followed: the global mimalloc allocator (fewer arena futexes across the per-core monoio workers plus the tokio side-runtime) and the on-core fast path with the finished-response memo, which removes pipeline work rather than speeding it up.

File egress: sendfile and kTLS

File egress receipts (1 MiB bodies, c=50)

loopback · 2026-09-09 · req/s

2,000 rps4,000 rps6,000 rps8,000 rpsplaintext H1 sendfilereq/s · plaintext H1 sendfile: 4,852 rps4,852TLS 1.3 H1, kTLS off (rustls)req/s · TLS 1.3 H1, kTLS off (rustls): 3,000 rps3,000TLS 1.3 H1, kTLS forced on (software, virtual NIC)req/s · TLS 1.3 H1, kTLS forced on (software, virtual NIC): 2,945 rps2,945

Software kTLS on a virtual GVE NIC is the expected-negative case; --ktls=auto refuses virtual NICs for this reason.

table view
req/s
plaintext H1 sendfile4,852 rps
TLS 1.3 H1, kTLS off (rustls)3,000 rps
TLS 1.3 H1, kTLS forced on (software, virtual NIC)2,945 rps

Plain-TCP H1 file bodies leave through bounded nonblocking sendfile(2); TLS 1.3 H1 can too under kernel TLS, but only when --ktls=auto finds a physical NIC with active tls-hw-tx-offload. The production host's virtual NIC reports off [fixed], and forcing software kTLS there measured slightly worse — which is exactly why auto refuses it. Details: Protocol stacks.

The codegen tier — PGO + mimalloc

Production binaries combine profile-guided optimization, the mimalloc allocator and -C target-cpu=native. The checked-in scripts/build-pgo.sh is the classic three-step — instrument, drive a representative loopback workload (h2 cache hits dominant, plus h2/h1 static and PHP), merge and rebuild — with frame pointers kept so the pprof endpoint can symbolize the live binary. Its workload and CPU tuning belong to one deployment; build a neutral release artifact separately.

!
A non-PGO benchmark is not a benchmark

Debug builds are ~5× slower and meaningless. Even a plain release build differs from PGO by enough (~10% from binary layout alone) to fake a regression: build both sides the same way and interleave runs. The bench harness (scripts/bench-suite.sh, scripts/bench-report.py) records CSVs under scripts/bench-results/.

Knobs that actually move production

LeverEffect
Page-cache opt-in coverage and TTLs (app side)The dominant lever: every hit removes a PHP render.
--page-cache-admit-threshold 1 with dictionariesCache on first sight when storage is cheap; watch httpjet_cache_hit_ratio_real.
--cf-send-zstdHands the CDN the cheapest encoding to produce; no browser-visible change.
Fast-memo eligibility of the docroot chainAn ineligible chain (see httpjet check) pays the full pipeline on every static request; fix the directive rather than disabling the memo.
--rewrite-ua-classifyCollapses UA diversity on UA-reading chains onto a handful of outcome-cache entries.
TLS resumptionFull handshakes dominate behind a CDN that does not re-offer tickets; ECDSA chains and the single AES-128-GCM suite keep them cheap.
PHP: opcache, worker count, slow endpointsUse --php-slow-log attribution; the pool is rarely the bottleneck (acquire p99.9 < 1 ms).

httpjet · documentation · synced to the source tree on 2026-10-13 (v0.0.5).
Source: github.com/faratech/httpjet · Home: httpjet.net · Found an error? Open an issue on the repository.