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.
Read the regime, not just the number
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
nginx could not complete the low-concurrency h2 cells in this harness (blank). Source: bench-results/versus5-dedicated-final-20260827.csv
table view
| httpjet | OpenLiteSpeed | nginx | |
|---|---|---|---|
| c=1 (1 KB) | 58 k rps | 50 k rps | — |
| c=50 | 908 k rps | 793 k rps | — |
| c=200 | 937 k rps | 687 k rps | — |
| c=1000 | 860 k rps | 878 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
httpjet runs the full .htaccess pipeline on every static request; the default-on fast-path memo is what closes the gap.
table view
| httpjet | OpenLiteSpeed | nginx | |
|---|---|---|---|
| HTTP/1.1 | 168.7 k rps | 169.1 k rps | 145.6 k rps |
| HTTP/2 | 298 k rps | 283.3 k rps | 125.4 k rps |
Fast-path memo, same binary A/B
forum chain · /robots.txt · c=100 · req/s
+43% h1, +81% h2. Byte-identity gate: scripts/fast_memo_parity_test.sh (192 cells, zero differences).
table view
| memo off | memo on | |
|---|---|---|
| HTTP/1.1 | 107 k rps | 153 k rps |
| HTTP/2 | 137 k rps | 248 k rps |
PHP over LSAPI, HTTP/1.1
loopback · PGO build · 2026-06 · req/s
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-world | 44.8 k rps | 38.5 k rps | 41.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
≈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
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 PHP | 8.96 ms |
| everything else (TLS, h2, rewrite 5.4 µs, cache lookup 3.2 µs, log) | 1.03 ms |
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.
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
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 analysisTwo 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
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 sendfile | 4,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.
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
| Lever | Effect |
|---|---|
| Page-cache opt-in coverage and TTLs (app side) | The dominant lever: every hit removes a PHP render. |
--page-cache-admit-threshold 1 with dictionaries | Cache on first sight when storage is cheap; watch httpjet_cache_hit_ratio_real. |
--cf-send-zstd | Hands the CDN the cheapest encoding to produce; no browser-visible change. |
| Fast-memo eligibility of the docroot chain | An ineligible chain (see httpjet check) pays the full pipeline on every static request; fix the directive rather than disabling the memo. |
--rewrite-ua-classify | Collapses UA diversity on UA-reading chains onto a handful of outcome-cache entries. |
| TLS resumption | Full 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 endpoints | Use --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.