Repository navigation
Performance
These tables are a same-harness, same-origin, same-runner-class reverse-proxy comparison — not a blog-post bake-off of mismatched labs.
-
Same load generator (
dotnet-httpclient), same origin process, same warmup/measure (2s / 8s), same concurrency ramp (8, 16, 32, 64), median of 3 GitHub Actions repeats. - Every reverse arm is three OS processes (load generator + origin child + proxy child). Origin-direct omits the proxy. Peers are never in-process with the client.
-
Same runner class per table:
windows-latest/ubuntu-latest/macos-15-intel(4-core-class). Laptop High-perf andmacos-latestare never mixed into these tables. -
YARP is
Yarp.ReverseProxy2.3.0 with equivalent TLS/ALPN. nginx uses streaming knobs (keepalive 256, buffering off). HAProxy (GPL v2 Community) and Envoy (Apache 2.0) are open-source terminate peers on the same loopback shape — Linux/macOS only; Windows cells are Not possible (no official HAProxy port; Envoy Windows support discontinued). Fair knobs: HAProxyhttp-reuse aggressive,maxconn 256,nbthread=CPU; Envoyconcurrency=CPU, cluster limits ~256, no access log, upstream TLS verify off for loopback CA. Linux nginx (mainline +http_v3) is authoritative; Windows nginx has no QUIC — those cells are Not possible, not losses. - MITM is TWP-only (CONNECT + forged certs). nginx/YARP cannot MITM; Lite/Full tables are overhead vs TWP reverse, not vs peers. There are no MITM charts.
- The product signal is TWP÷YARP (gated ≥ 0.75 reverse) and MITM÷Reverse (Lite ≥ 0.50, Full ≥ 0.50), not absolute RPS. nginx / HAProxy / Envoy are wiki and chart peers only — no CI gate. Absolute RPS moves with runner heat; ratios are the claim.
- Product fast paths (session-lite, skip-poll, compressed relay) are in-tree defaults for interception-off reverse — the same class of work YARP/nginx do. The harness does not disable TWP safety that peers also skip, and it does not retune to pass gates (PERF-GATES.md).
-
MITM leaf keys are a shared RSA/ECDSA pair per
CertificateManager(one key for all forged leaves). That is an intentional RPS tradeoff: compromise of that material affects every host minted by that manager. Reverse/YARP/nginx paths do not mint leaves.
Numbers below are Release measurements with RpsLoadProbe. Native terminate peers: nginx, HAProxy, Envoy (where the OS supports them).
For pooling knobs and certificate first-visit tuning, see Performance and pooling. For the local cool A/B lab, laptop tables, and profiling notes, see Performance Profiling.
- Measurement environment
- Windows — Titanium vs nginx vs HAProxy vs Envoy vs YARP
- Linux — Titanium vs nginx vs HAProxy vs Envoy vs YARP
- macOS — Titanium vs nginx vs HAProxy vs Envoy vs YARP
- Editions (CLI / Plus / Intercept)
- Cross-version (7.0 vs 6.0)
-
Heavier reverse workloads
- Windows — heavier reverse GET (64 KiB / 256 KiB)
- Linux — heavier reverse GET (64 KiB / 256 KiB)
- Windows — POST 64 KiB request + 64 KiB response
- Linux — POST 64 KiB request + 64 KiB response
- Windows — lossy / high-RTT (H2 HOL / H3 loss)
- Linux — lossy / high-RTT (H2 HOL / H3 loss)
- Architecture-sensitive
- TLS termination cost (H1 TLS → cleartext origin)
- Other measurements
- Raising limits on large hosts
All three OS use the 4-core-class public-repo GitHub-hosted runners: Windows / Linux at 4 vCPU / 16 GiB / 14 GB SSD, macOS at macos-15-intel 4-core / 14 GB (not macos-latest). Same harness knobs (workflow_dispatch RPS saturation: warmup 2s / measure 8s; concurrency 8, 16, 32, 64; median of 3 repeats; --stop-on-slo-fail default on). Every --ramp arm is three OS processes (parent load generator + origin child + proxy child), except origin-direct arms (load gen + origin only). Prefer TWP÷YARP / TWP÷nginx ratios over absolute RPS.
Laptop High-perf / cool-paired Windows numbers live on Performance Local Lab. Do not mix those absolutes into the tables below.
| Tier | Mode | When |
|---|---|---|
| Daily / per-PR | compare-spot |
minutes |
| Milestone |
compare-terminate / compare-matrix
|
~1–2h |
| Editions | compare-editions |
~60 min (CLI / Plus / Intercept stress arms) |
| Cross-version (Gate 2) | compare-cross-version |
~1–2h vs committed 6.0 baselines |
| Release / wiki | compare-product |
~3–4h |
| Heavier tables |
compare-bodies / post / lossy / arch / bridges / tls-cost
|
dispatch independently |
See PERF-GATES.md.
| OS | Windows Server (GitHub-hosted windows-latest) |
| CPU | 4 logical processors |
| RAM | 16 GiB |
| Runtime | .NET 10.0.x |
| nginx | nginx/Windows 1.31.3 (same-OS only; no QUIC) |
| HAProxy | Not possible on Windows (no official port) |
| Envoy | Not possible on Windows (upstream discontinued Windows builds) |
| YARP | Yarp.ReverseProxy 2.3.0 |
| Harness | RpsLoadProbe Release; median of 3 repeats |
| OS | Ubuntu 24.04.x LTS |
| CPU | 4 logical processors (AMD EPYC; runners this pass were 7763 / 9V74) |
| RAM | 16 GiB |
| Runtime | .NET 10.0.11 |
| nginx | nginx/1.31.4 (nginx.org mainline, --with-http_v3_module) |
| HAProxy | HAProxy 3.2.23 built with USE_QUIC (GHA; Ubuntu distro 2.8 is not QUIC-capable) |
| Envoy | pinned GitHub release static binary 1.36.7 (HTTP/3 compiled in) |
| YARP | Yarp.ReverseProxy 2.3.0 |
| Harness | RpsLoadProbe Release; median of 3 repeats where noted |
| OS | macOS 15 (GitHub-hosted macos-15-intel, Intel x86_64) |
| CPU | 4 logical processors |
| RAM | 14 GB |
| Runtime | .NET 10.0.x |
| nginx | Homebrew nginx with --with-http_v3_module (workflow fails if missing) |
| HAProxy | Homebrew haproxy with USE_QUIC (workflow fails if missing; 3.2.23 osx source fallback) |
| Envoy | Homebrew bottle when present; else pinned darwin-amd64 1.36.7 (official GitHub assets are Linux-only). HTTP/3 compiled in. |
| MsQuic | Homebrew libmsquic + openssl@3 on DYLD_LIBRARY_PATH / DYLD_FALLBACK_LIBRARY_PATH (QuicListener.IsSupported) |
| YARP | Yarp.ReverseProxy 2.3.0 |
| Harness | RpsLoadProbe Release; median of 3 repeats where noted |
Do not use macos-latest (Apple Silicon, 3-core / 7 GB) for publishable saturation numbers.
Calibration for the shared 4 vCPU loopback shape: how close client + origin are to saturated before ranking reverse peers. Tiny keep-alive GET. Median of 3 repeats @ 803a69bc — 34394853191. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Block A % of origin-HttpClient uses median peak RPS. Blocks B/C use peer÷YARP / ÷nginx on median peak (not % of H1 origin). RPS cells embed median RSS / CPU for the proxy child plus its full descendant tree (serve-proxy → nginx master → workers); origin-direct samples the origin child. Product matrices below use matched dotnet-httpclient only (not bombardier).
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-saturation| Arm | Generator | Notes |
|---|---|---|
origin-direct |
dotnet-httpclient |
No proxy — origin ceiling under the same client used for publishable ratios |
origin-direct-bombardier |
bombardier |
External client check (CI installs bombardier) |
bare-reverse-http1 |
dotnet-httpclient |
Thin C# H1 reverse (BareHttp1ReverseProxy) — .NET runtime / loopback ceiling for a three-process reverse hop; not a product peer |
nginx-reverse-http1 / yarp-reverse-http1 / twp-reverse-http1
|
dotnet-httpclient |
Product peers (medals among these three only) |
Windows (windows-latest)
| Arm | Generator | Sustain | Peak | % of origin-HttpClient |
|---|---|---|---|---|
| origin-direct | dotnet-httpclient |
63,571 (55 MiB / 42.1% CPU) |
63,571 (55 MiB / 42.1% CPU) |
100.0% |
| origin-direct-bombardier | bombardier |
48,794 (56 MiB / 24.8% CPU) |
48,794 (56 MiB / 24.8% CPU) |
76.8% |
| bare-reverse-http1 | dotnet-httpclient |
31,458 (57 MiB / 45.7% CPU) |
31,458 (57 MiB / 45.7% CPU) |
49.5% |
| nginx-reverse-http1 | dotnet-httpclient |
19,660 (125 MiB / 24.9% CPU) |
19,660 (125 MiB / 24.9% CPU) |
30.9% |
| yarp-reverse-http1 | dotnet-httpclient |
27,402 (91 MiB / 49.8% CPU) |
27,402 (91 MiB / 49.8% CPU) |
43.1% |
| twp-reverse-http1 | dotnet-httpclient | 🥇 31,891 (73 MiB / 48.5% CPU) |
31,891 (73 MiB / 48.5% CPU) |
50.2% |
Linux (ubuntu-latest)
| Arm | Generator | Sustain | Peak | % of origin-HttpClient |
|---|---|---|---|---|
| origin-direct | dotnet-httpclient |
71,840 (80 MiB / 42.0% CPU) |
71,840 (80 MiB / 42.0% CPU) |
100.0% |
| origin-direct-bombardier | bombardier |
43,758 (80 MiB / 35.4% CPU) |
43,758 (80 MiB / 35.4% CPU) |
60.9% |
| bare-reverse-http1 | dotnet-httpclient |
33,382 (70 MiB / 45.0% CPU) |
33,382 (70 MiB / 45.0% CPU) |
46.5% |
| nginx-reverse-http1 | dotnet-httpclient | 🥇 39,834 (75 MiB / 40.7% CPU) |
39,834 (75 MiB / 40.7% CPU) |
55.4% |
| yarp-reverse-http1 | dotnet-httpclient |
28,584 (115 MiB / 50.6% CPU) |
28,584 (115 MiB / 50.6% CPU) |
39.8% |
| twp-reverse-http1 | dotnet-httpclient |
33,681 (89 MiB / 50.7% CPU) |
33,681 (89 MiB / 50.7% CPU) |
46.9% |
Reverse peers are about 50–46% of the origin-direct HttpClient peak on this runner class (Win TWP 50.0%, Lin TWP 45.6%). Prefer the % column over absolute RPS across runs. Bare and origin-direct are controls (not medal peers).
Peer ratios (÷YARP / ÷nginx) on median peak; RPS cells embed (MiB / CPU%).
Windows (windows-latest)
| Arm | Generator | Sustain | Peak | ÷YARP | ÷nginx |
|---|---|---|---|---|---|
| nginx-reverse-http2 | dotnet-httpclient |
11,563 (141 MiB / 24.3% CPU) |
11,563 (141 MiB / 24.3% CPU) |
0.33× | 1.00× |
| yarp-reverse-http2 | dotnet-httpclient |
35,473 (91 MiB / 53.0% CPU) |
35,473 (91 MiB / 53.0% CPU) |
1.00× | 3.07× |
| twp-reverse-http2-cleartext | dotnet-httpclient | 🥇 40,219 (105 MiB / 52.9% CPU) |
40,219 (105 MiB / 52.9% CPU) |
1.13× | 3.48× |
Linux (ubuntu-latest)
| Arm | Generator | Sustain | Peak | ÷YARP | ÷nginx |
|---|---|---|---|---|---|
| nginx-reverse-http2 | dotnet-httpclient |
15,477 (100 MiB / 19.4% CPU) |
15,477 (100 MiB / 19.4% CPU) |
0.52× | 1.00× |
| yarp-reverse-http2 | dotnet-httpclient |
29,740 (122 MiB / 50.1% CPU) |
29,740 (122 MiB / 50.1% CPU) |
1.00× | 1.92× |
| twp-reverse-http2-cleartext | dotnet-httpclient | 🥇 34,805 (115 MiB / 53.0% CPU) |
34,805 (115 MiB / 53.0% CPU) |
1.17× | 2.25× |
Same layout as Block B. Requires QuicListener. nginx needs http_v3_module (Windows nginx has no QUIC). HAProxy needs USE_QUIC (GHA Linux/macOS require it). Envoy 1.20+ includes HTTP/3.
Windows (windows-latest)
| Arm | Generator | Sustain | Peak | ÷YARP | ÷nginx |
|---|---|---|---|---|---|
| nginx-reverse-http3-cleartext | dotnet-httpclient | Not possible (no QUIC) | Not possible (no QUIC) | — | — |
| yarp-reverse-http3-cleartext | dotnet-httpclient |
17,560 (159 MiB / 48.2% CPU) |
17,560 (159 MiB / 48.2% CPU) |
1.00× | — |
| twp-reverse-http3-cleartext | dotnet-httpclient | 🥇 19,686 (108 MiB / 43.9% CPU) |
19,686 (108 MiB / 43.9% CPU) |
1.12× | — |
Linux (ubuntu-latest)
| Arm | Generator | Sustain | Peak | ÷YARP | ÷nginx |
|---|---|---|---|---|---|
| nginx-reverse-http3-cleartext | dotnet-httpclient |
0 (106 MiB / 22.7% CPU) |
15,453 (106 MiB / 22.7% CPU) |
0.81× | 1.00× |
| yarp-reverse-http3-cleartext | dotnet-httpclient |
19,089 (183 MiB / 50.6% CPU) |
19,089 (183 MiB / 50.6% CPU) |
1.00× | 1.24× |
| twp-reverse-http3-cleartext | dotnet-httpclient | 🥇 21,267 (146 MiB / 50.8% CPU) |
21,267 (146 MiB / 50.8% CPU) |
1.11× | 1.38× |
How to read the tables
-
Reverse = bare transparent fixed-forward (no TWP plugins / interception). nginx knobs match TWP/YARP streaming (
keepalive 256,proxy_buffering off). MITM = TWP-only table on the same Client×Origin wires: Lite = no-op handlers (unchanged-lite finish reuses reverse compressed relay); Full = mutating handlers that append up to four unique headers per direction (RPS harness adds one; product usesMitmCompressedRelayHelper— no probe name in library code). Remove/replace/non-unique header growth and body mutation still force full decode/re-encode. nginx/YARP cannot MITM. HTTP/3 has no cleartext client (QUIC always encrypted). - Sustainable = last concurrency that still met error/latency SLOs. Peak = highest RPS in that ramp.
- 🥇 = best among TWP / nginx / YARP on Reverse rows (or saturation blocks): highest RPS; on an RPS tie, lower Memory (RSS) then lower CPU%. MITM is TWP-only. Lite÷Reverse / Full÷Reverse = TWP MITM lite or full sustain ÷ TWP Reverse sustain on the same Client×Origin from the same
compare-productjob. - Not possible = product cannot do that path. Not measured = path exists but no published number yet for that OS.
- Product refresh:
compare-product@803a69bc— 34394847149 (Win/Linux) + 34400795387 (macOS). Heavier/saturation/tls:
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-product
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-bodies
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-post
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-lossy
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-tls-cost
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-arch
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-saturationClient / origin: HTTP version and whether TLS is used (plain = cleartext, TLS = encrypted, QUIC = HTTP/3).
Median of 3 repeats on windows-latest (4 vCPU / 16 GiB). Bare reverse 5×5 @ 803a69bc — compare-product 34394847149. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Prefer TWP÷peer ratios over absolute RPS. RPS cells include median RSS / CPU at the peak-RPS step as <br><sub>(MiB / CPU%)</sub>. nginx terminate peers use keepalive 256 + streaming buffers. HAProxy / Envoy are Linux-only peers (no official Windows port). Laptop High-perf / cool-paired numbers stay on the local lab.
Load generators: Reverse inbound H3 arms use dotnet-httpclient (http_version=3.0, RequestVersionExact). nginx/Windows is same-OS only (no QUIC). HAProxy/Envoy are Linux-only terminate peers.

| Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain | 🥇 25054 (76 MiB / 49.4% CPU) |
🥇 25054 (76 MiB / 49.4% CPU) |
13771 (125 MiB / 24.8% CPU) |
13771 (125 MiB / 24.8% CPU) |
Not possible | Not possible | Not possible | Not possible |
21602 (85 MiB / 50.6% CPU) |
21602 (85 MiB / 50.6% CPU) |
| HTTP/1 · plain | HTTP/1 · TLS | 🥇 21000 (86 MiB / 51% CPU) |
🥇 21000 (86 MiB / 51% CPU) |
8661 (134 MiB / 24.8% CPU) |
8661 (134 MiB / 24.8% CPU) |
Not possible | Not possible | Not possible | Not possible |
19518 (97 MiB / 49.8% CPU) |
19518 (97 MiB / 49.8% CPU) |
| HTTP/1 · plain | HTTP/2 · plain | 🥇 36791 (111 MiB / 49.3% CPU) |
🥇 36791 (111 MiB / 49.3% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
33115 (91 MiB / 49.4% CPU) |
33115 (91 MiB / 49.4% CPU) |
| HTTP/1 · plain | HTTP/2 · TLS | 🥇 32151 (116 MiB / 47.1% CPU) |
🥇 32151 (116 MiB / 47.1% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
29920 (96 MiB / 47% CPU) |
29920 (96 MiB / 47% CPU) |
| HTTP/1 · plain | HTTP/3 · QUIC | 🥇 18702 (102 MiB / 51% CPU) |
🥇 18702 (102 MiB / 51% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
18353 (116 MiB / 50.4% CPU) |
18353 (116 MiB / 50.4% CPU) |
| HTTP/1 · TLS | HTTP/1 · plain | 🥇 20185 (86 MiB / 51.3% CPU) |
🥇 20185 (86 MiB / 51.3% CPU) |
8911 (141 MiB / 24.6% CPU) |
8911 (141 MiB / 24.6% CPU) |
Not possible | Not possible | Not possible | Not possible |
17946 (101 MiB / 48.8% CPU) |
17946 (101 MiB / 48.8% CPU) |
| HTTP/1 · TLS | HTTP/1 · TLS | 🥇 18608 (91 MiB / 50.8% CPU) |
🥇 18608 (91 MiB / 50.8% CPU) |
7028 (142 MiB / 24.8% CPU) |
7028 (142 MiB / 24.8% CPU) |
Not possible | Not possible | Not possible | Not possible |
16843 (110 MiB / 50.9% CPU) |
16843 (110 MiB / 50.9% CPU) |
| HTTP/1 · TLS | HTTP/2 · plain | 🥇 27459 (113 MiB / 46.7% CPU) |
🥇 27459 (113 MiB / 46.7% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
26830 (105 MiB / 47.2% CPU) |
26830 (105 MiB / 47.2% CPU) |
| HTTP/1 · TLS | HTTP/2 · TLS | 🥇 25383 (116 MiB / 46% CPU) |
🥇 25383 (116 MiB / 46% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
24244 (112 MiB / 47% CPU) |
24244 (112 MiB / 47% CPU) |
| HTTP/1 · TLS | HTTP/3 · QUIC | 🥇 15985 (106 MiB / 49.8% CPU) |
🥇 15985 (106 MiB / 49.8% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
15343 (131 MiB / 50.2% CPU) |
15343 (131 MiB / 50.2% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 35887 (93 MiB / 54.7% CPU) |
🥇 35887 (93 MiB / 54.7% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
32446 (85 MiB / 52.9% CPU) |
32446 (85 MiB / 52.9% CPU) |
| HTTP/2 · plain | HTTP/1 · TLS | 🥇 30762 (100 MiB / 52.7% CPU) |
🥇 30762 (100 MiB / 52.7% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
27539 (97 MiB / 53.9% CPU) |
27539 (97 MiB / 53.9% CPU) |
| HTTP/2 · plain | HTTP/2 · plain | 🥇 110729 (58 MiB / 28.7% CPU) |
🥇 110729 (58 MiB / 28.7% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
64788 (97 MiB / 49.9% CPU) |
64788 (97 MiB / 49.9% CPU) |
| HTTP/2 · plain | HTTP/2 · TLS | 🥇 88024 (66 MiB / 28.4% CPU) |
🥇 88024 (66 MiB / 28.4% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
55602 (101 MiB / 46.3% CPU) |
55602 (101 MiB / 46.3% CPU) |
| HTTP/2 · plain | HTTP/3 · QUIC | 🥇 31069 (121 MiB / 52.3% CPU) |
🥇 31069 (121 MiB / 52.3% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
29187 (126 MiB / 53.7% CPU) |
29187 (126 MiB / 53.7% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 34633 (103 MiB / 54.1% CPU) |
🥇 34633 (103 MiB / 54.1% CPU) |
8211 (141 MiB / 24.3% CPU) |
8211 (141 MiB / 24.3% CPU) |
Not possible | Not possible | Not possible | Not possible |
28909 (92 MiB / 51.8% CPU) |
28909 (92 MiB / 51.8% CPU) |
| HTTP/2 · TLS | HTTP/1 · TLS | 🥇 29580 (98 MiB / 52% CPU) |
🥇 29580 (98 MiB / 52% CPU) |
6502 (143 MiB / 24.7% CPU) |
6502 (143 MiB / 24.7% CPU) |
Not possible | Not possible | Not possible | Not possible |
25330 (99 MiB / 52.8% CPU) |
25330 (99 MiB / 52.8% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 103464 (78 MiB / 29.6% CPU) |
🥇 103464 (78 MiB / 29.6% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
55941 (101 MiB / 51.4% CPU) |
55941 (101 MiB / 51.4% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 84540 (75 MiB / 29.2% CPU) |
🥇 84540 (75 MiB / 29.2% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
49628 (98 MiB / 49.7% CPU) |
49628 (98 MiB / 49.7% CPU) |
| HTTP/2 · TLS | HTTP/3 · QUIC | 🥇 30994 (129 MiB / 52.8% CPU) |
🥇 30994 (129 MiB / 52.8% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
26171 (125 MiB / 52.6% CPU) |
26171 (125 MiB / 52.6% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
14596 (104 MiB / 46% CPU) |
14596 (104 MiB / 46% CPU) |
Not measured | Not measured | Not possible | Not possible | Not possible | Not possible | 🥇 14805 (141 MiB / 52.1% CPU) |
🥇 14805 (141 MiB / 52.1% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
12985 (110 MiB / 45.6% CPU) |
12985 (110 MiB / 45.6% CPU) |
Not measured | Not measured | Not possible | Not possible | Not possible | Not possible | 🥇 13441 (144 MiB / 52.8% CPU) |
🥇 13441 (144 MiB / 52.8% CPU) |
| HTTP/3 · QUIC | HTTP/2 · plain | 🥇 34995 (137 MiB / 48.4% CPU) |
🥇 34995 (137 MiB / 48.4% CPU) |
Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible | Not possible | Not possible | Not possible |
23912 (167 MiB / 47.3% CPU) |
23912 (167 MiB / 47.3% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 31822 (132 MiB / 46.7% CPU) |
🥇 31822 (132 MiB / 46.7% CPU) |
Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible | Not possible | Not possible | Not possible |
24706 (160 MiB / 46.6% CPU) |
24706 (160 MiB / 46.6% CPU) |
| HTTP/3 · QUIC | HTTP/3 · QUIC | Not measured | Not measured | Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible | 🥇 11596 (149 MiB / 53.2% CPU) |
🥇 11596 (149 MiB / 53.2% CPU) |
Same Client×Origin wires with interception on (compare-product 34394847149). Lite = no-op handlers (unchanged-lite finish). Full = append-only header mutation (harness: one probe header each way; product: generic append-only relay via MitmCompressedRelayHelper). nginx/HAProxy/Envoy/YARP cannot MITM. Lite÷Reverse / Full÷Reverse vs bare reverse (same job). Completion gate: Lite ≥ 0.50× and Full ≥ 0.50× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.75× (no terminate-peer gate).
v1 append-only relay (2026-08-27): Pre-fix H2→H2 Full÷Reverse was 0.13–0.16× (32960766249). Post-fix @ df172718: H2 plain→H2 plain Full 0.77–0.79×, H3→H1 Full 0.91–0.93×, all MITM arms ≥ 0.70× on median of 33041445371, 33055267086, 33055272140.
v2 drop-only + non-unique append (2026-08-27): MitmStaticRebuildHelper rebuilds static HPACK/QPACK after 1–4 unique header drops; trailing non-unique appends stay on compressed relay. @ 803a69bc: all MITM arms ≥ 0.70× on GHA median (33087088466, 33087091622, 33105885748 Linux; 33087085235, 33087088466, 33087091622 Windows). H2 plain→H2 plain Full 0.77–0.79× (Win) / 0.78× (Lin).
| Client | Origin | Lite sustain | Full sustain | Lite÷Reverse | Full÷Reverse |
|---|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain |
24644 (83 MiB / 51% CPU) |
24535 (81 MiB / 53.4% CPU) |
0.98× | 0.98× |
| HTTP/1 · plain | HTTP/1 · TLS |
20856 (94 MiB / 56% CPU) |
20127 (94 MiB / 53.2% CPU) |
0.99× | 0.96× |
| HTTP/1 · plain | HTTP/2 · plain |
36417 (106 MiB / 48.9% CPU) |
35496 (108 MiB / 50.7% CPU) |
0.99× | 0.96× |
| HTTP/1 · plain | HTTP/2 · TLS |
31704 (125 MiB / 48.3% CPU) |
31094 (123 MiB / 46.6% CPU) |
0.99× | 0.97× |
| HTTP/1 · plain | HTTP/3 · QUIC |
18089 (109 MiB / 51% CPU) |
17875 (104 MiB / 51.9% CPU) |
0.97× | 0.96× |
| HTTP/1 · TLS | HTTP/1 · plain |
19586 (94 MiB / 51% CPU) |
19437 (95 MiB / 51% CPU) |
0.97× | 0.96× |
| HTTP/1 · TLS | HTTP/1 · TLS |
18398 (95 MiB / 50.5% CPU) |
18156 (95 MiB / 46.8% CPU) |
0.99× | 0.98× |
| HTTP/1 · TLS | HTTP/2 · plain |
27946 (115 MiB / 45.4% CPU) |
27317 (116 MiB / 46.5% CPU) |
1.02× | 0.99× |
| HTTP/1 · TLS | HTTP/2 · TLS |
25320 (124 MiB / 46.4% CPU) |
25183 (131 MiB / 46.8% CPU) |
1× | 0.99× |
| HTTP/1 · TLS | HTTP/3 · QUIC |
15601 (111 MiB / 50.4% CPU) |
15558 (106 MiB / 50.2% CPU) |
0.98× | 0.97× |
| HTTP/2 · plain | HTTP/1 · plain |
34477 (88 MiB / 55.2% CPU) |
34306 (103 MiB / 53% CPU) |
0.96× | 0.96× |
| HTTP/2 · plain | HTTP/1 · TLS |
27209 (100 MiB / 52.6% CPU) |
29339 (104 MiB / 55.9% CPU) |
0.88× | 0.95× |
| HTTP/2 · plain | HTTP/2 · plain |
83369 (68 MiB / 40.1% CPU) |
81696 (72 MiB / 42.7% CPU) |
0.75× | 0.74× |
| HTTP/2 · plain | HTTP/2 · TLS |
70034 (73 MiB / 37.5% CPU) |
68939 (77 MiB / 37.9% CPU) |
0.8× | 0.78× |
| HTTP/2 · plain | HTTP/3 · QUIC |
27269 (114 MiB / 51.3% CPU) |
30995 (126 MiB / 53.5% CPU) |
0.88× | 1× |
| HTTP/2 · TLS | HTTP/1 · plain |
32528 (103 MiB / 53.2% CPU) |
33215 (102 MiB / 56.9% CPU) |
0.94× | 0.96× |
| HTTP/2 · TLS | HTTP/1 · TLS |
25532 (102 MiB / 53.5% CPU) |
28655 (101 MiB / 56.9% CPU) |
0.86× | 0.97× |
| HTTP/2 · TLS | HTTP/2 · plain |
77426 (82 MiB / 37.9% CPU) |
76384 (86 MiB / 40.8% CPU) |
0.75× | 0.74× |
| HTTP/2 · TLS | HTTP/2 · TLS |
67233 (82 MiB / 38.5% CPU) |
66345 (85 MiB / 38% CPU) |
0.8× | 0.78× |
| HTTP/2 · TLS | HTTP/3 · QUIC |
26522 (125 MiB / 51.4% CPU) |
29808 (121 MiB / 54.1% CPU) |
0.86× | 0.96× |
| HTTP/3 · QUIC | HTTP/1 · plain |
13525 (111 MiB / 45.6% CPU) |
13929 (109 MiB / 47.2% CPU) |
0.93× | 0.95× |
| HTTP/3 · QUIC | HTTP/1 · TLS |
11708 (125 MiB / 45.1% CPU) |
11802 (123 MiB / 47.6% CPU) |
0.9× | 0.91× |
| HTTP/3 · QUIC | HTTP/2 · plain |
23029 (132 MiB / 39.2% CPU) |
30754 (138 MiB / 49.1% CPU) |
0.66× | 0.88× |
| HTTP/3 · QUIC | HTTP/2 · TLS |
26954 (127 MiB / 46.6% CPU) |
28523 (138 MiB / 47.6% CPU) |
0.85× | 0.9× |
Median of 3 repeats on ubuntu-latest (4 vCPU / 16 GiB). Bare reverse 5×5 @ 803a69bc — compare-product 34394847149. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Linux nginx is the authoritative nginx baseline. HAProxy (3.2 USE_QUIC) and Envoy (GitHub release, HTTP/3 compiled in) run on the same loopback shape as nginx/YARP. nginx terminate peers use keepalive 256 + streaming buffers. The RPS workflow installs nginx.org mainline (http_v3_module), a QUIC-enabled HAProxy, Envoy, and libmsquic. Prefer ratios over absolute RPS.

| Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain |
32270 (89 MiB / 50.8% CPU) |
32270 (89 MiB / 50.8% CPU) |
🥇 38903 (75 MiB / 41.4% CPU) |
🥇 38903 (75 MiB / 41.4% CPU) |
37745 (64 MiB / 41.5% CPU) |
37745 (64 MiB / 41.5% CPU) |
20047 (115 MiB / 62.9% CPU) |
20047 (115 MiB / 62.9% CPU) |
28012 (116 MiB / 50% CPU) |
28012 (116 MiB / 50% CPU) |
| HTTP/1 · plain | HTTP/1 · TLS |
23960 (106 MiB / 50.4% CPU) |
23960 (106 MiB / 50.4% CPU) |
28728 (91 MiB / 42.6% CPU) |
28728 (91 MiB / 42.6% CPU) |
🥇 28744 (66 MiB / 42.7% CPU) |
🥇 28744 (66 MiB / 42.7% CPU) |
17105 (118 MiB / 59.3% CPU) |
17105 (118 MiB / 59.3% CPU) |
21520 (130 MiB / 50.5% CPU) |
21520 (130 MiB / 50.5% CPU) |
| HTTP/1 · plain | HTTP/2 · plain | 🥇 40144 (128 MiB / 51.6% CPU) |
🥇 40144 (128 MiB / 51.6% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
35437 (124 MiB / 49% CPU) |
35437 (124 MiB / 49% CPU) |
| HTTP/1 · plain | HTTP/2 · TLS | 🥇 32388 (147 MiB / 50.2% CPU) |
🥇 32388 (147 MiB / 50.2% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
29872 (133 MiB / 48% CPU) |
29872 (133 MiB / 48% CPU) |
| HTTP/1 · plain | HTTP/3 · QUIC | 🥇 23483 (134 MiB / 53.6% CPU) |
🥇 23483 (134 MiB / 53.6% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) |
21721 (153 MiB / 49.1% CPU) |
21721 (153 MiB / 49.1% CPU) |
| HTTP/1 · TLS | HTTP/1 · plain |
22861 (108 MiB / 50.1% CPU) |
22861 (108 MiB / 50.1% CPU) |
27039 (99 MiB / 41.9% CPU) |
27039 (99 MiB / 41.9% CPU) |
🥇 28240 (81 MiB / 42.5% CPU) |
🥇 28240 (81 MiB / 42.5% CPU) |
16614 (127 MiB / 58.5% CPU) |
16614 (127 MiB / 58.5% CPU) |
20267 (132 MiB / 50.7% CPU) |
20267 (132 MiB / 50.7% CPU) |
| HTTP/1 · TLS | HTTP/1 · TLS |
19375 (111 MiB / 49.1% CPU) |
19375 (111 MiB / 49.1% CPU) |
22435 (102 MiB / 41.4% CPU) |
22435 (102 MiB / 41.4% CPU) |
🥇 23122 (82 MiB / 42.7% CPU) |
🥇 23122 (82 MiB / 42.7% CPU) |
15290 (127 MiB / 56.3% CPU) |
15290 (127 MiB / 56.3% CPU) |
17094 (140 MiB / 50.1% CPU) |
17094 (140 MiB / 50.1% CPU) |
| HTTP/1 · TLS | HTTP/2 · plain | 🥇 28474 (141 MiB / 49.7% CPU) |
🥇 28474 (141 MiB / 49.7% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
25107 (150 MiB / 48.6% CPU) |
25107 (150 MiB / 48.6% CPU) |
| HTTP/1 · TLS | HTTP/2 · TLS | 🥇 24122 (158 MiB / 48.2% CPU) |
🥇 24122 (158 MiB / 48.2% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
22003 (146 MiB / 48.3% CPU) |
22003 (146 MiB / 48.3% CPU) |
| HTTP/1 · TLS | HTTP/3 · QUIC | 🥇 17746 (146 MiB / 52% CPU) |
🥇 17746 (146 MiB / 52% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) |
16498 (162 MiB / 49.6% CPU) |
16498 (162 MiB / 49.6% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 36472 (118 MiB / 53.7% CPU) |
🥇 36472 (118 MiB / 53.7% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
34584 (115 MiB / 50.3% CPU) |
34584 (115 MiB / 50.3% CPU) |
| HTTP/2 · plain | HTTP/1 · TLS | 🥇 27662 (121 MiB / 51.6% CPU) |
🥇 27662 (121 MiB / 51.6% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
25603 (126 MiB / 50.7% CPU) |
25603 (126 MiB / 50.7% CPU) |
| HTTP/2 · plain | HTTP/2 · plain | 🥇 85454 (91 MiB / 37.4% CPU) |
🥇 85454 (91 MiB / 37.4% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
48247 (121 MiB / 47.3% CPU) |
48247 (121 MiB / 47.3% CPU) |
| HTTP/2 · plain | HTTP/2 · TLS | 🥇 57924 (95 MiB / 35.4% CPU) |
🥇 57924 (95 MiB / 35.4% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
39194 (130 MiB / 46% CPU) |
39194 (130 MiB / 46% CPU) |
| HTTP/2 · plain | HTTP/3 · QUIC | 🥇 27971 (144 MiB / 50.6% CPU) |
🥇 27971 (144 MiB / 50.6% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) |
26128 (151 MiB / 47% CPU) |
26128 (151 MiB / 47% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 34068 (115 MiB / 52.8% CPU) |
🥇 34068 (115 MiB / 52.8% CPU) |
15239 (100 MiB / 19.4% CPU) |
15239 (100 MiB / 19.4% CPU) |
33334 (80 MiB / 23.2% CPU) |
33334 (80 MiB / 23.2% CPU) |
13969 (128 MiB / 22.8% CPU) |
13969 (128 MiB / 22.8% CPU) |
29075 (122 MiB / 49.8% CPU) |
29075 (122 MiB / 49.8% CPU) |
| HTTP/2 · TLS | HTTP/1 · TLS | 🥇 26402 (119 MiB / 50.8% CPU) |
🥇 26402 (119 MiB / 50.8% CPU) |
12391 (109 MiB / 19.9% CPU) |
12391 (109 MiB / 19.9% CPU) |
24693 (82 MiB / 24.1% CPU) |
24693 (82 MiB / 24.1% CPU) |
12301 (127 MiB / 23.4% CPU) |
12301 (127 MiB / 23.4% CPU) |
23198 (132 MiB / 50.1% CPU) |
23198 (132 MiB / 50.1% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 73478 (101 MiB / 37.1% CPU) |
🥇 73478 (101 MiB / 37.1% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
38506 (129 MiB / 46.7% CPU) |
38506 (129 MiB / 46.7% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 54367 (104 MiB / 35.2% CPU) |
🥇 54367 (104 MiB / 35.2% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
33117 (124 MiB / 45.8% CPU) |
33117 (124 MiB / 45.8% CPU) |
| HTTP/2 · TLS | HTTP/3 · QUIC | 🥇 25984 (147 MiB / 50% CPU) |
🥇 25984 (147 MiB / 50% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) |
22404 (157 MiB / 47.3% CPU) |
22404 (157 MiB / 47.3% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 20702 (144 MiB / 50.2% CPU) |
🥇 20702 (144 MiB / 50.2% CPU) |
0 (106 MiB / 22.5% CPU) |
15098 (106 MiB / 22.5% CPU) |
Not measured | Not measured | Not measured | Not measured |
18609 (181 MiB / 50.6% CPU) |
18609 (181 MiB / 50.6% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 16018 (159 MiB / 48.2% CPU) |
🥇 16018 (159 MiB / 48.2% CPU) |
0 (115 MiB / 23% CPU) |
11518 (115 MiB / 23% CPU) |
Not measured | Not measured | Not measured | Not measured |
15137 (195 MiB / 51.3% CPU) |
15137 (195 MiB / 51.3% CPU) |
| HTTP/3 · QUIC | HTTP/2 · plain | 🥇 28575 (156 MiB / 53.1% CPU) |
🥇 28575 (156 MiB / 53.1% CPU) |
Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) |
23780 (194 MiB / 48.3% CPU) |
23780 (194 MiB / 48.3% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 24706 (150 MiB / 50.5% CPU) |
🥇 24706 (150 MiB / 50.5% CPU) |
Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) |
21222 (196 MiB / 47.4% CPU) |
21222 (196 MiB / 47.4% CPU) |
| HTTP/3 · QUIC | HTTP/3 · QUIC | 🥇 20615 (157 MiB / 46.7% CPU) |
🥇 20615 (157 MiB / 46.7% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) |
16062 (197 MiB / 48.1% CPU) |
16062 (197 MiB / 48.1% CPU) |
Same Client×Origin wires with interception on (compare-product 34394847149). Lite = no-op handlers (unchanged-lite finish). Full = append-only header mutation (harness: one probe header each way; product: generic append-only relay via MitmCompressedRelayHelper). nginx/HAProxy/Envoy/YARP cannot MITM. Lite÷Reverse / Full÷Reverse vs bare reverse (same job). Completion gate: Lite ≥ 0.50× and Full ≥ 0.50× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.75× (no terminate-peer gate).
v1 append-only relay (2026-08-27): Pre-fix H2→H2 Full÷Reverse was 0.13–0.16× (32960766249). Post-fix @ df172718: H2 plain→H2 plain Full 0.77–0.79×, H3→H1 Full 0.91–0.93×, all MITM arms ≥ 0.70× on median of 33041445371, 33055267086, 33055272140.
v2 drop-only + non-unique append (2026-08-27): MitmStaticRebuildHelper rebuilds static HPACK/QPACK after 1–4 unique header drops; trailing non-unique appends stay on compressed relay. @ 803a69bc: all MITM arms ≥ 0.70× on GHA median (33087088466, 33087091622, 33105885748 Linux; 33087085235, 33087088466, 33087091622 Windows). H2 plain→H2 plain Full 0.77–0.79× (Win) / 0.78× (Lin).
| Client | Origin | Lite sustain | Full sustain | Lite÷Reverse | Full÷Reverse |
|---|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain |
31264 (93 MiB / 50.7% CPU) |
30781 (92 MiB / 51% CPU) |
0.97× | 0.95× |
| HTTP/1 · plain | HTTP/1 · TLS |
23794 (112 MiB / 51.6% CPU) |
23050 (114 MiB / 51.6% CPU) |
0.99× | 0.96× |
| HTTP/1 · plain | HTTP/2 · plain |
39350 (131 MiB / 53% CPU) |
38263 (133 MiB / 53% CPU) |
0.98× | 0.95× |
| HTTP/1 · plain | HTTP/2 · TLS |
31906 (144 MiB / 51% CPU) |
31443 (148 MiB / 50.1% CPU) |
0.99× | 0.97× |
| HTTP/1 · plain | HTTP/3 · QUIC |
23218 (139 MiB / 55.4% CPU) |
22352 (137 MiB / 54% CPU) |
0.99× | 0.95× |
| HTTP/1 · TLS | HTTP/1 · plain |
22665 (114 MiB / 50.4% CPU) |
22185 (116 MiB / 50.5% CPU) |
0.99× | 0.97× |
| HTTP/1 · TLS | HTTP/1 · TLS |
19487 (116 MiB / 50% CPU) |
18935 (116 MiB / 49.7% CPU) |
1.01× | 0.98× |
| HTTP/1 · TLS | HTTP/2 · plain |
27599 (146 MiB / 50.5% CPU) |
26821 (143 MiB / 51.8% CPU) |
0.97× | 0.94× |
| HTTP/1 · TLS | HTTP/2 · TLS |
23792 (168 MiB / 48.1% CPU) |
22951 (168 MiB / 49.3% CPU) |
0.99× | 0.95× |
| HTTP/1 · TLS | HTTP/3 · QUIC |
18152 (160 MiB / 52.4% CPU) |
17638 (155 MiB / 52.5% CPU) |
1.02× | 0.99× |
| HTTP/2 · plain | HTTP/1 · plain |
36441 (125 MiB / 54.8% CPU) |
35355 (117 MiB / 54.1% CPU) |
1× | 0.97× |
| HTTP/2 · plain | HTTP/1 · TLS |
27060 (123 MiB / 52.5% CPU) |
26132 (125 MiB / 52.3% CPU) |
0.98× | 0.94× |
| HTTP/2 · plain | HTTP/2 · plain |
64369 (93 MiB / 42.3% CPU) |
57731 (94 MiB / 40.9% CPU) |
0.75× | 0.68× |
| HTTP/2 · plain | HTTP/2 · TLS |
48576 (96 MiB / 40.2% CPU) |
45891 (97 MiB / 39.3% CPU) |
0.84× | 0.79× |
| HTTP/2 · plain | HTTP/3 · QUIC |
28238 (144 MiB / 51.1% CPU) |
26735 (144 MiB / 51.1% CPU) |
1.01× | 0.96× |
| HTTP/2 · TLS | HTTP/1 · plain |
33103 (126 MiB / 54.3% CPU) |
32632 (124 MiB / 53.8% CPU) |
0.97× | 0.96× |
| HTTP/2 · TLS | HTTP/1 · TLS |
25975 (123 MiB / 51.8% CPU) |
24579 (124 MiB / 51.6% CPU) |
0.98× | 0.93× |
| HTTP/2 · TLS | HTTP/2 · plain |
56900 (110 MiB / 40.9% CPU) |
52948 (111 MiB / 40.8% CPU) |
0.77× | 0.72× |
| HTTP/2 · TLS | HTTP/2 · TLS |
45768 (103 MiB / 38.6% CPU) |
42914 (112 MiB / 39.6% CPU) |
0.84× | 0.79× |
| HTTP/2 · TLS | HTTP/3 · QUIC |
25991 (150 MiB / 50.7% CPU) |
25507 (165 MiB / 50.4% CPU) |
1× | 0.98× |
| HTTP/3 · QUIC | HTTP/1 · plain |
19635 (141 MiB / 50.8% CPU) |
18750 (147 MiB / 51.1% CPU) |
0.95× | 0.91× |
| HTTP/3 · QUIC | HTTP/1 · TLS |
15168 (170 MiB / 48.4% CPU) |
14938 (159 MiB / 49.2% CPU) |
0.95× | 0.93× |
| HTTP/3 · QUIC | HTTP/2 · plain |
27751 (165 MiB / 55.2% CPU) |
26961 (168 MiB / 55.4% CPU) |
0.97× | 0.94× |
| HTTP/3 · QUIC | HTTP/2 · TLS |
23431 (153 MiB / 51.9% CPU) |
22946 (156 MiB / 51.5% CPU) |
0.95× | 0.93× |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
19546 (157 MiB / 49.1% CPU) |
19411 (151 MiB / 49.3% CPU) |
0.95× | 0.94× |
Numbers are filled by tools/RpsLoadProbe/apply-wiki-paste.ps1 after compare-product on macos-15-intel (placeholder headers match the Linux table shape).
Median of 3 repeats on macos-15-intel (4-core / 14 GB). Bare reverse 5×5 @ 803a69bc — compare-product 34400795387 (macOS retry; Win/Linux 34394847149). Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Prefer TWP÷peer ratios over absolute RPS. RPS cells include median RSS / CPU at the peak-RPS step as <br><sub>(MiB / CPU%)</sub>. The RPS workflow installs Homebrew nginx (http_v3_module), Homebrew HAProxy with USE_QUIC (3.2 source fallback), Envoy (Homebrew bottle or pinned darwin-amd64 1.36.7), Homebrew libmsquic (+ DYLD_*), and YARP. Do not publish from macos-latest (3-core / 7 GB).

| Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain |
15153 (82 MiB / 34.5% CPU) |
15153 (82 MiB / 34.5% CPU) |
11466 (52 MiB / 16% CPU) |
11466 (52 MiB / 16% CPU) |
🥇 19500 (47 MiB / 25.2% CPU) |
🥇 19500 (47 MiB / 25.2% CPU) |
4395 (73 MiB / 21.6% CPU) |
4395 (73 MiB / 21.6% CPU) |
14271 (103 MiB / 37.7% CPU) |
14271 (103 MiB / 37.7% CPU) |
| HTTP/1 · plain | HTTP/1 · TLS | 🥇 16905 (93 MiB / 37.1% CPU) |
🥇 16905 (93 MiB / 37.1% CPU) |
7482 (72 MiB / 17.5% CPU) |
7482 (72 MiB / 17.5% CPU) |
15247 (53 MiB / 28.6% CPU) |
15247 (53 MiB / 28.6% CPU) |
3614 (75 MiB / 21.7% CPU) |
3614 (75 MiB / 21.7% CPU) |
15333 (118 MiB / 39.8% CPU) |
15333 (118 MiB / 39.8% CPU) |
| HTTP/1 · plain | HTTP/2 · plain |
21595 (91 MiB / 34.9% CPU) |
21595 (91 MiB / 34.9% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible | 🥇 26267 (109 MiB / 36.2% CPU) |
🥇 26267 (109 MiB / 36.2% CPU) |
| HTTP/1 · plain | HTTP/2 · TLS | 🥇 20225 (108 MiB / 36.8% CPU) |
🥇 20225 (108 MiB / 36.8% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
16519 (114 MiB / 35.9% CPU) |
16519 (114 MiB / 35.9% CPU) |
| HTTP/1 · plain | HTTP/3 · QUIC |
6297 (91 MiB / 46.6% CPU) |
6297 (91 MiB / 46.6% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | 🥇 7119 (109 MiB / 32.9% CPU) |
🥇 7119 (109 MiB / 32.9% CPU) |
| HTTP/1 · TLS | HTTP/1 · plain | 🥇 14450 (92 MiB / 34.7% CPU) |
🥇 14450 (92 MiB / 34.7% CPU) |
7448 (73 MiB / 14.6% CPU) |
7448 (73 MiB / 14.6% CPU) |
13196 (64 MiB / 28.6% CPU) |
13196 (64 MiB / 28.6% CPU) |
6054 (79 MiB / 36.1% CPU) |
6054 (79 MiB / 36.1% CPU) |
9983 (114 MiB / 34.8% CPU) |
9983 (114 MiB / 34.8% CPU) |
| HTTP/1 · TLS | HTTP/1 · TLS | 🥇 12591 (97 MiB / 34.6% CPU) |
🥇 12591 (97 MiB / 34.6% CPU) |
8262 (81 MiB / 20.8% CPU) |
8262 (81 MiB / 20.8% CPU) |
11036 (66 MiB / 29.6% CPU) |
11036 (66 MiB / 29.6% CPU) |
3942 (81 MiB / 31.9% CPU) |
3942 (81 MiB / 31.9% CPU) |
10262 (122 MiB / 34.5% CPU) |
10262 (122 MiB / 34.5% CPU) |
| HTTP/1 · TLS | HTTP/2 · plain | 🥇 16192 (96 MiB / 35.7% CPU) |
🥇 16192 (96 MiB / 35.7% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
16148 (120 MiB / 32.1% CPU) |
16148 (120 MiB / 32.1% CPU) |
| HTTP/1 · TLS | HTTP/2 · TLS |
12371 (145 MiB / 33.2% CPU) |
12371 (145 MiB / 33.2% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible | 🥇 14753 (121 MiB / 33% CPU) |
🥇 14753 (121 MiB / 33% CPU) |
| HTTP/1 · TLS | HTTP/3 · QUIC |
4458 (104 MiB / 49% CPU) |
4458 (104 MiB / 49% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | 🥇 6082 (121 MiB / 34.8% CPU) |
🥇 6082 (121 MiB / 34.8% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 21171 (89 MiB / 38.9% CPU) |
🥇 21171 (89 MiB / 38.9% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
20417 (104 MiB / 38.5% CPU) |
20417 (104 MiB / 38.5% CPU) |
| HTTP/2 · plain | HTTP/1 · TLS | 🥇 19464 (95 MiB / 41.7% CPU) |
🥇 19464 (95 MiB / 41.7% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
16927 (118 MiB / 41% CPU) |
16927 (118 MiB / 41% CPU) |
| HTTP/2 · plain | HTTP/2 · plain | 🥇 46942 (73 MiB / 27.6% CPU) |
🥇 46942 (73 MiB / 27.6% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
33850 (108 MiB / 35.7% CPU) |
33850 (108 MiB / 35.7% CPU) |
| HTTP/2 · plain | HTTP/2 · TLS | 🥇 42076 (76 MiB / 28.3% CPU) |
🥇 42076 (76 MiB / 28.3% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
31738 (109 MiB / 35.8% CPU) |
31738 (109 MiB / 35.8% CPU) |
| HTTP/2 · plain | HTTP/3 · QUIC |
6771 (96 MiB / 48.5% CPU) |
6771 (96 MiB / 48.5% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | 🥇 7184 (116 MiB / 34.2% CPU) |
🥇 7184 (116 MiB / 34.2% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 15896 (91 MiB / 39.2% CPU) |
🥇 15896 (91 MiB / 39.2% CPU) |
9042 (72 MiB / 12.9% CPU) |
9042 (72 MiB / 12.9% CPU) |
8392 (65 MiB / 17.8% CPU) |
8392 (65 MiB / 17.8% CPU) |
4189 (81 MiB / 20.9% CPU) |
4189 (81 MiB / 20.9% CPU) |
14938 (111 MiB / 39.2% CPU) |
14938 (111 MiB / 39.2% CPU) |
| HTTP/2 · TLS | HTTP/1 · TLS | 🥇 17180 (96 MiB / 42% CPU) |
🥇 17180 (96 MiB / 42% CPU) |
9604 (92 MiB / 15.5% CPU) |
9604 (92 MiB / 15.5% CPU) |
7198 (66 MiB / 17.9% CPU) |
7198 (66 MiB / 17.9% CPU) |
4175 (82 MiB / 20.3% CPU) |
4175 (82 MiB / 20.3% CPU) |
15520 (121 MiB / 39.5% CPU) |
15520 (121 MiB / 39.5% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 40490 (81 MiB / 29% CPU) |
🥇 40490 (81 MiB / 29% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
30100 (110 MiB / 36% CPU) |
30100 (110 MiB / 36% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 31123 (84 MiB / 27% CPU) |
🥇 31123 (84 MiB / 27% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
28290 (114 MiB / 36.2% CPU) |
28290 (114 MiB / 36.2% CPU) |
| HTTP/2 · TLS | HTTP/3 · QUIC |
6440 (108 MiB / 40.5% CPU) |
6440 (108 MiB / 40.5% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | 🥇 7422 (120 MiB / 36.3% CPU) |
🥇 7422 (120 MiB / 36.3% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
5779 (101 MiB / 40.8% CPU) |
5779 (101 MiB / 40.8% CPU) |
0 (62 MiB / 10.9% CPU) |
8227 (62 MiB / 10.9% CPU) |
🥇 9880 (65 MiB / 19.6% CPU) |
🥇 9880 (65 MiB / 19.6% CPU) |
Not measured | Not measured |
7469 (189 MiB / 35.5% CPU) |
7469 (189 MiB / 35.5% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
4933 (115 MiB / 41.3% CPU) |
4933 (115 MiB / 41.3% CPU) |
0 (66 MiB / 12% CPU) |
6050 (66 MiB / 12% CPU) |
🥇 6739 (68 MiB / 18.2% CPU) |
🥇 6739 (68 MiB / 18.2% CPU) |
Not measured | Not measured |
6056 (185 MiB / 35.8% CPU) |
6056 (185 MiB / 35.8% CPU) |
| HTTP/3 · QUIC | HTTP/2 · plain |
6173 (98 MiB / 39.2% CPU) |
6173 (98 MiB / 39.2% CPU) |
Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | 🥇 8531 (167 MiB / 35% CPU) |
🥇 8531 (167 MiB / 35% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS |
6677 (106 MiB / 41% CPU) |
6677 (106 MiB / 41% CPU) |
Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | Not possible (no H3 to H2) | 🥇 8365 (177 MiB / 34.2% CPU) |
🥇 8365 (177 MiB / 34.2% CPU) |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
4537 (97 MiB / 35.9% CPU) |
4537 (97 MiB / 35.9% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | Not possible (no QUIC) | 🥇 4746 (169 MiB / 33.8% CPU) |
🥇 4746 (169 MiB / 33.8% CPU) |
Same Client×Origin wires with interception on (compare-product 34394847149). Lite = no-op handlers (unchanged-lite finish). Full = append-only header mutation (harness: one probe header each way; product: generic append-only relay via MitmCompressedRelayHelper). nginx/HAProxy/Envoy/YARP cannot MITM. Lite÷Reverse / Full÷Reverse vs bare reverse (same job). Completion gate: Lite ≥ 0.50× and Full ≥ 0.50× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.75× (no terminate-peer gate).
v1 append-only relay (2026-08-27): Pre-fix H2→H2 Full÷Reverse was 0.13–0.16× (32960766249). Post-fix @ df172718: H2 plain→H2 plain Full 0.77–0.79×, H3→H1 Full 0.91–0.93×, all MITM arms ≥ 0.70× on median of 33041445371, 33055267086, 33055272140.
v2 drop-only + non-unique append (2026-08-27): MitmStaticRebuildHelper rebuilds static HPACK/QPACK after 1–4 unique header drops; trailing non-unique appends stay on compressed relay. @ 803a69bc: all MITM arms ≥ 0.70× on GHA median (33087088466, 33087091622, 33105885748 Linux; 33087085235, 33087088466, 33087091622 Windows). H2 plain→H2 plain Full 0.77–0.79× (Win) / 0.78× (Lin).
| Client | Origin | Lite sustain | Full sustain | Lite÷Reverse | Full÷Reverse |
|---|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain |
16913 (82 MiB / 36% CPU) |
20795 (85 MiB / 35.8% CPU) |
1.12× | 1.37× |
| HTTP/1 · plain | HTTP/1 · TLS |
13436 (95 MiB / 37.2% CPU) |
17140 (96 MiB / 37.5% CPU) |
0.79× | 1.01× |
| HTTP/1 · plain | HTTP/2 · plain |
16965 (91 MiB / 39.2% CPU) |
22762 (94 MiB / 37.9% CPU) |
0.79× | 1.05× |
| HTTP/1 · plain | HTTP/2 · TLS |
15602 (103 MiB / 38.7% CPU) |
14831 (103 MiB / 36.1% CPU) |
0.77× | 0.73× |
| HTTP/1 · plain | HTTP/3 · QUIC |
4422 (93 MiB / 47.7% CPU) |
5232 (92 MiB / 45.1% CPU) |
0.7× | 0.83× |
| HTTP/1 · TLS | HTTP/1 · plain |
12258 (94 MiB / 34% CPU) |
13496 (96 MiB / 33.5% CPU) |
0.85× | 0.93× |
| HTTP/1 · TLS | HTTP/1 · TLS |
11302 (98 MiB / 34.7% CPU) |
11699 (99 MiB / 35% CPU) |
0.9× | 0.93× |
| HTTP/1 · TLS | HTTP/2 · plain |
11398 (98 MiB / 36.2% CPU) |
11835 (99 MiB / 38.1% CPU) |
0.7× | 0.73× |
| HTTP/1 · TLS | HTTP/2 · TLS |
9850 (153 MiB / 35.3% CPU) |
11254 (143 MiB / 35.1% CPU) |
0.8× | 0.91× |
| HTTP/1 · TLS | HTTP/3 · QUIC |
3489 (128 MiB / 49.3% CPU) |
3404 (103 MiB / 47.1% CPU) |
0.78× | 0.76× |
| HTTP/2 · plain | HTTP/1 · plain |
19071 (87 MiB / 34.8% CPU) |
16949 (89 MiB / 39.7% CPU) |
0.9× | 0.8× |
| HTTP/2 · plain | HTTP/1 · TLS |
14079 (95 MiB / 41.4% CPU) |
18441 (97 MiB / 40.6% CPU) |
0.72× | 0.95× |
| HTTP/2 · plain | HTTP/2 · plain |
36658 (78 MiB / 32.6% CPU) |
40098 (76 MiB / 33.8% CPU) |
0.78× | 0.85× |
| HTTP/2 · plain | HTTP/2 · TLS |
26984 (81 MiB / 31.6% CPU) |
38890 (83 MiB / 33% CPU) |
0.64× | 0.92× |
| HTTP/2 · plain | HTTP/3 · QUIC |
6433 (94 MiB / 44% CPU) |
6663 (94 MiB / 44.5% CPU) |
0.95× | 0.98× |
| HTTP/2 · TLS | HTTP/1 · plain |
24647 (91 MiB / 38.6% CPU) |
25719 (93 MiB / 40.6% CPU) |
1.55× | 1.62× |
| HTTP/2 · TLS | HTTP/1 · TLS |
20502 (97 MiB / 42.1% CPU) |
18318 (96 MiB / 39.4% CPU) |
1.19× | 1.07× |
| HTTP/2 · TLS | HTTP/2 · plain |
41277 (86 MiB / 31.4% CPU) |
40008 (87 MiB / 32.4% CPU) |
1.02× | 0.99× |
| HTTP/2 · TLS | HTTP/2 · TLS |
38592 (86 MiB / 31.3% CPU) |
39114 (87 MiB / 33% CPU) |
1.24× | 1.26× |
| HTTP/2 · TLS | HTTP/3 · QUIC |
7387 (107 MiB / 41.2% CPU) |
7135 (106 MiB / 42.9% CPU) |
1.15× | 1.11× |
| HTTP/3 · QUIC | HTTP/1 · plain |
6204 (102 MiB / 38.2% CPU) |
4773 (103 MiB / 40.4% CPU) |
1.07× | 0.83× |
| HTTP/3 · QUIC | HTTP/1 · TLS |
6132 (115 MiB / 42.5% CPU) |
4390 (117 MiB / 39% CPU) |
1.24× | 0.89× |
| HTTP/3 · QUIC | HTTP/2 · plain |
6846 (99 MiB / 40.2% CPU) |
5659 (99 MiB / 40.9% CPU) |
1.11× | 0.92× |
| HTTP/3 · QUIC | HTTP/2 · TLS |
7116 (109 MiB / 40.2% CPU) |
6209 (106 MiB / 40% CPU) |
1.07× | 0.93× |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
4382 (95 MiB / 36.9% CPU) |
3684 (95 MiB / 39.4% CPU) |
0.97× | 0.81× |
Note: twp-reverse-http1 and other library rows use Core with probe-tuned settings (no logging, no Via header, probe-warmed certs). Edition rows use titanium run -c twp.yaml product defaults — prefer the ÷baseline ratio column over absolute RPS. Inspector GUI is not spawnable in the harness; session-path overhead is twp-cli-intercept-http1 (route RequestHeaderSet transform).
Gates prioritize SLO survival under load and reasonable overhead vs baseline. Pre-origin Plus middleware (CIDR/WAF/JWT/rate-limit) runs on H1 terminate-lite without SessionEventArgs; JWT caches successful bearer validations. Thresholds — see PERF-GATES.md.
Median of 3 repeats @ 6d2a7c9d. Source: Actions 33259699099 (compare-editions). Warmup 2s / measure 8s; concurrency 8–64; sustain = median peak RPS among SLO-pass steps @ c=64. RPS cells include (MiB / CPU%) at that step.
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-editions
pwsh tools/RpsLoadProbe/validate-edition-gates.ps1 -CsvPath tools/RpsLoadProbe/results/rps-ramp-*.csv| Arm | Win sustain | Win peak | Linux sustain | Linux peak | Win÷ | Lin÷ | Gate |
|---|---|---|---|---|---|---|---|
twp-cli-reverse-http1 vs library |
32214 (118 MiB / 43.4% CPU) |
32219 |
47734 (151 MiB / 49.0% CPU) |
47844 | 1.02× | 1.04× | ≥ 0.80× |
twp-cli-reverse-http1-tls vs library TLS |
26495 (138 MiB / 48.3% CPU) |
26651 |
36932 (174 MiB / 48.5% CPU) |
37200 | 1.01× | 1.00× | ≥ 0.80× |
twp-cli-reverse-http1-route vs CLI |
32394 (120 MiB / 47.7% CPU) |
32428 |
47369 (149 MiB / 49.2% CPU) |
47398 | 1.01× | 0.99× | ≥ 0.90× |
twp-cli-plus-base-http1 vs CLI |
32475 (123 MiB / 46.0% CPU) |
32695 |
47322 (154 MiB / 49.8% CPU) |
47744 | 1.01× | 0.99× | ≥ 0.90× |
twp-cli-plus-cache-http1 (cold) vs CLI |
34029 (118 MiB / 64.5% CPU) |
34460 |
49409 (148 MiB / 62.3% CPU) |
50104 | 1.06× | 1.04× | ≥ 0.70× |
twp-cli-intercept-http1 vs CLI |
25219 (124 MiB / 54.0% CPU) |
25377 |
35969 (155 MiB / 51.2% CPU) |
35977 | 0.78× | 0.75× | ≥ 0.70× |
twp-cli-plus-waf-http1 vs CLI |
31653 (124 MiB / 46.8% CPU) |
31837 |
46367 (151 MiB / 49.6% CPU) |
46685 | 0.98× | 0.97× | ≥ 0.80× |
twp-cli-plus-cidr-http1 vs CLI |
31730 (123 MiB / 51.1% CPU) |
31757 |
46828 (150 MiB / 50.2% CPU) |
47093 | 0.98× | 0.98× | ≥ 0.80× |
twp-cli-plus-jwt-http1 vs CLI |
30326 (138 MiB / 46.6% CPU) |
30386 |
43906 (179 MiB / 49.5% CPU) |
44433 | 0.94× | 0.92× | ≥ 0.70× |
twp-cli-plus-ratelimit-http1 vs CLI |
31713 (123 MiB / 48.8% CPU) |
31944 |
46435 (151 MiB / 49.4% CPU) |
46860 | 0.98× | 0.97× | ≥ 0.80× |
twp-cli-plus-resilience-http1 vs CLI |
32373 (128 MiB / 50.5% CPU) |
32416 |
47302 (155 MiB / 49.5% CPU) |
47654 | 1.00× | 0.99× | ≥ 0.85× |
twp-cli-plus-discovery-file-http1 vs CLI |
32209 (121 MiB / 46.5% CPU) |
32332 |
47485 (151 MiB / 49.1% CPU) |
47770 | 1.00× | 0.99× | ≥ 0.80× |
twp-cli-plus-metrics-scrape-http1 vs CLI |
32192 (126 MiB / 44.8% CPU) |
32427 |
47348 (159 MiB / 49.5% CPU) |
48665 | 1.00× | 0.99× | ≥ 0.80× |
twp-cli-plus-cache-hit-http1 vs cache cold |
34015 (117 MiB / 66.0% CPU) |
34022 |
49153 (149 MiB / 63.1% CPU) |
49358 | 1.00× | 0.99× | ≥ 0.90× |
twp-cli-static-http1 vs CLI |
53078 (109 MiB / 52.1% CPU) |
53980 |
78333 (150 MiB / 48.4% CPU) |
82955 | 1.65× | 1.64× | ≥ 0.85× |
twp-cli-logging-http1 vs CLI |
32440 (119 MiB / 47.4% CPU) |
32581 |
47624 (150 MiB / 49.3% CPU) |
48604 | 1.01× | 1.00× | ≥ 0.90× |
twp-cli-lb-leasttime-http1 vs route |
29649 (134 MiB / 54.7% CPU) |
30386 |
44790 (180 MiB / 50.6% CPU) |
44825 | 0.92× | 0.95× | ≥ 0.85× |
twp-cli-dialect-twp-http1 vs CLI |
32461 (114 MiB / 49.7% CPU) |
32780 |
47898 (145 MiB / 49.3% CPU) |
48154 | 1.01× | 1.00× | ≥ 0.90× |
validate-edition-gates.ps1 passed on both OS for this run. Library baselines @ c=64 (same job): Win H1 31561 / TLS 26128; Linux H1 45818 / TLS 36869. Laptop smoke ratios stay on Performance Local Lab — Editions.
Gate 2 prerequisite before the v7.0.0 tag: run compare-cross-version (reverse matrix, routes unset) on develop and compare against committed 6.0 baselines from GHA 33087088466 (tools/RpsLoadProbe/results/baseline-6.0-win.csv / baseline-6.0-linux.csv).
| Gate | Threshold |
|---|---|
| Peer-normalized RPS (when YARP peer exists) |
(TWP÷YARP)_7 ÷ (TWP÷YARP)_6 ≥ 0.90 or current (TWP÷YARP) ≥ 0.90
|
| Absolute RPS floor (TWP arms) |
7.0 ÷ 6.0 ≥ 0.70 (runner heat — peers move with the box) |
| RSS |
7.0 ÷ 6.0 ≤ 1.20 per TWP arm @ c=64 |
nginx/YARP absolute RPS is not gated. See validate-cross-version.ps1.
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-cross-version
pwsh tools/RpsLoadProbe/validate-cross-version.ps1 `
-BaselineCsv tools/RpsLoadProbe/results/baseline-6.0-win.csv `
-CurrentCsv tools/RpsLoadProbe/results/rps-ramp-*.csvMedian of 3 @ 0ef6d4dd. Source: Actions 33270571908. Sustain RPS @ c=64 (SLO-pass median). Absolute 7.0÷6.0 ≈ 0.96–1.00× on same-protocol arms; peer-norm ≈ 0.90–1.03×.
| Arm | Win 6.0 | Win 7.0 | Win÷ | Linux 6.0 | Linux 7.0 | Lin÷ |
|---|---|---|---|---|---|---|
twp-reverse-http1 |
32525 | 32213 | 0.99× | 32755 | 31932 | 0.97× |
twp-reverse-http1-tls |
26461 | 26570 | 1.00× | 22784 | 22351 | 0.98× |
twp-reverse-http2 |
76038 | 75316 | 0.99× | 47690 | 46678 | 0.98× |
twp-reverse-http2-cleartext |
40347 | 40236 | 1.00× | 34556 | 33316 | 0.96× |
twp-reverse-http3 |
17529 | 17380 | 0.99× | 19468 | 19423 | 1.00× |
twp-reverse-http3-cleartext |
19956 | 19529 | 0.98× | 20395 | 20070 | 0.98× |
yarp-reverse-http1 (peer) |
27504 | 27254 | 0.99× | 27713 | 27102 | 0.98× |
yarp-reverse-http2 (peer) |
35553 | 35189 | 0.99× | 29111 | 28733 | 0.99× |
validate-cross-version.ps1 passed on both OS CSVs (RSS floor 1.20; peer-norm also passes when current TWP÷YARP ≥ 0.90). MITM arms live in compare-product / compare-mitm, not this reverse-only matrix.
Separate from the tiny-GET matrix. Same measurement environments. Modes: compare-bodies, compare-post, compare-lossy, compare-tls-cost, compare-arch in RpsLoadProbe. Dispatch each independently via workflow_dispatch (no need to re-run full compare-product). Charts: render-heavier-charts.py (rps-heavier-*-{windows,linux}.png). PUT with the same body is the same proxy work as POST; DELETE with no body matches GET — only POST is published. Bodies/POST/lossy stay half-duplex. compare-arch is the slow-consumer / early-response / duplex set. Laptop numbers are on Performance Local Lab; CI medians go in the tables below. Tables below are pre–5-peer publish; re-paste with paste-heavier-wiki.py after the next heavier GHA runs for HAProxy/Envoy columns.
Lossy link = userspace shim (not kernel netem): TCP gets per-buffer delay + occasional whole-connection stalls (honest HOL for multiplexed H2); UDP gets per-datagram delay + drops (QUIC). compare-lossy publishes H1/H2/H3; H3 is where the protocol design is supposed to matter.

Median of 3 repeats on windows-latest @ 803a69bc. Source: Actions 34394859668 (compare-bodies). Warmup 2s / measure 8s. RPS cells include (MiB / CPU%) footprints.
| Body | Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 64 KiB | HTTP/1 · TLS | HTTP/1 · plain | 🥇 8,414 (113 MiB / 45.9% CPU) |
8,414 (113 MiB / 45.9% CPU) |
594 (141 MiB / 24.8% CPU) |
594 (141 MiB / 24.8% CPU) |
Not possible | Not possible | Not possible | Not possible |
7,558 (129 MiB / 50.3% CPU) |
7,558 (129 MiB / 50.3% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/1 · plain | 🥇 7,926 (165 MiB / 46.6% CPU) |
7,926 (165 MiB / 46.6% CPU) |
572 (141 MiB / 24.8% CPU) |
572 (141 MiB / 24.8% CPU) |
Not possible | Not possible | Not possible | Not possible |
6,605 (133 MiB / 50.5% CPU) |
6,605 (133 MiB / 50.5% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 3,966 (135 MiB / 40.1% CPU) |
3,966 (135 MiB / 40.1% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
3,796 (195 MiB / 49.4% CPU) |
3,796 (195 MiB / 49.4% CPU) |
| 256 KiB | HTTP/1 · TLS | HTTP/1 · plain | 🥇 2,766 (118 MiB / 45.0% CPU) |
2,766 (118 MiB / 45.0% CPU) |
0 (142 MiB / 24.4% CPU) |
150 (142 MiB / 24.4% CPU) |
Not possible | Not possible | Not possible | Not possible |
2,421 (135 MiB / 50.8% CPU) |
2,421 (135 MiB / 50.8% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/1 · plain | 🥇 2,529 (153 MiB / 41.2% CPU) |
2,529 (153 MiB / 41.2% CPU) |
0 (141 MiB / 24.9% CPU) |
136 (141 MiB / 24.9% CPU) |
Not possible | Not possible | Not possible | Not possible |
1,640 (133 MiB / 46.9% CPU) |
1,640 (133 MiB / 46.9% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 1,166 (111 MiB / 38.6% CPU) |
1,166 (111 MiB / 38.6% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
1,043 (166 MiB / 43.8% CPU) |
1,043 (166 MiB / 43.8% CPU) |
nginx/Windows collapses on large reverse bodies in this harness; treat as same-OS only. H1 TLS 64 KiB ≈ 1.11× YARP; 256 KiB ≈ 1.23×. H2→H1 64 KiB ≈ 1.21×; H3→H1 64 KiB ≈ 1.18×.

Median of 3 repeats @ 803a69bc. Source: Actions 34394859668 (compare-bodies). Warmup 2s / measure 8s.
| Body | Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 64 KiB | HTTP/1 · TLS | HTTP/1 · plain |
8,706 (176 MiB / 44.5% CPU) |
8,706 (176 MiB / 44.5% CPU) |
5,827 (99 MiB / 49.4% CPU) |
5,827 (99 MiB / 49.4% CPU) |
🥇 10,468 (81 MiB / 38.2% CPU) |
10,468 (81 MiB / 38.2% CPU) |
8,499 (131 MiB / 43.5% CPU) |
8,499 (131 MiB / 43.5% CPU) |
7,253 (171 MiB / 47.9% CPU) |
7,253 (171 MiB / 47.9% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/1 · plain |
6,098 (239 MiB / 40.3% CPU) |
6,098 (239 MiB / 40.3% CPU) |
2,095 (100 MiB / 17.0% CPU) |
2,095 (100 MiB / 17.0% CPU) |
🥇 6,926 (84 MiB / 24.3% CPU) |
6,926 (84 MiB / 24.3% CPU) |
5,335 (141 MiB / 23.3% CPU) |
5,335 (141 MiB / 23.3% CPU) |
5,303 (163 MiB / 46.7% CPU) |
5,303 (163 MiB / 46.7% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 6,060 (188 MiB / 44.0% CPU) |
6,060 (188 MiB / 44.0% CPU) |
1,819 (129 MiB / 24.3% CPU) |
1,819 (129 MiB / 24.3% CPU) |
Not measured | Not measured | Not measured | Not measured |
4,675 (226 MiB / 51.4% CPU) |
4,675 (226 MiB / 51.4% CPU) |
| 256 KiB | HTTP/1 · TLS | HTTP/1 · plain |
2,840 (130 MiB / 35.9% CPU) |
2,840 (130 MiB / 35.9% CPU) |
1,852 (99 MiB / 52.3% CPU) |
1,852 (99 MiB / 52.3% CPU) |
🥇 3,126 (81 MiB / 31.9% CPU) |
3,126 (81 MiB / 31.9% CPU) |
2,804 (145 MiB / 32.4% CPU) |
2,804 (145 MiB / 32.4% CPU) |
2,265 (167 MiB / 44.6% CPU) |
2,265 (167 MiB / 44.6% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/1 · plain |
1,564 (195 MiB / 31.4% CPU) |
1,564 (195 MiB / 31.4% CPU) |
663 (99 MiB / 22.6% CPU) |
663 (99 MiB / 22.6% CPU) |
🥇 1,894 (84 MiB / 23.0% CPU) |
1,894 (84 MiB / 23.0% CPU) |
1,879 (161 MiB / 20.2% CPU) |
1,879 (161 MiB / 20.2% CPU) |
1,487 (165 MiB / 42.6% CPU) |
1,487 (165 MiB / 42.6% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 1,544 (155 MiB / 43.3% CPU) |
1,544 (155 MiB / 43.3% CPU) |
0 (120 MiB / 21.9% CPU) |
127 (120 MiB / 21.9% CPU) |
Not measured | Not measured | Not measured | Not measured |
1,327 (220 MiB / 47.8% CPU) |
1,327 (220 MiB / 47.8% CPU) |
On this GHA pass TWP÷YARP H1 TLS ≈ 1.23× (64 KiB) / 1.28× (256 KiB); H2→H1 ≈ 1.23× / 1.16×; H3→H1 ≈ 1.27× / 1.13×. TWP÷nginx H1 TLS ≈ 1.43 / 1.58. Absolute RPS swings by VM; prefer ratios.

Median of 3 repeats on windows-latest @ 803a69bc. Source: Actions 34394866049 (compare-post).
| Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain | 🥇 5,742 (100 MiB / 44.0% CPU) |
5,742 (100 MiB / 44.0% CPU) |
360 (142 MiB / 24.7% CPU) |
360 (142 MiB / 24.7% CPU) |
Not possible | Not possible | Not possible | Not possible |
4,046 (138 MiB / 56.9% CPU) |
4,046 (138 MiB / 56.9% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 4,046 (177 MiB / 48.0% CPU) |
4,046 (177 MiB / 48.0% CPU) |
342 (143 MiB / 24.5% CPU) |
342 (143 MiB / 24.5% CPU) |
Not possible | Not possible | Not possible | Not possible |
3,452 (132 MiB / 52.2% CPU) |
3,452 (132 MiB / 52.2% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,018 (174 MiB / 42.4% CPU) |
2,018 (174 MiB / 42.4% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
1,999 (213 MiB / 49.3% CPU) |
1,999 (213 MiB / 49.3% CPU) |
TWP leads H1 POST (1.5× YARP), H2 POST (1.2× YARP), and H3 POST (~1.1× YARP).

Median of 3 repeats @ 803a69bc. Source: Actions 34394866049 (compare-post).
| Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain |
9,319 (132 MiB / 41.0% CPU) |
9,319 (132 MiB / 41.0% CPU) |
7,612 (102 MiB / 44.0% CPU) |
7,612 (102 MiB / 44.0% CPU) |
🥇 11,038 (82 MiB / 35.8% CPU) |
11,038 (82 MiB / 35.8% CPU) |
9,568 (130 MiB / 41.8% CPU) |
9,568 (130 MiB / 41.8% CPU) |
6,084 (177 MiB / 53.6% CPU) |
6,084 (177 MiB / 53.6% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 5,655 (226 MiB / 45.7% CPU) |
5,655 (226 MiB / 45.7% CPU) |
3,237 (119 MiB / 21.8% CPU) |
3,237 (119 MiB / 21.8% CPU) |
4,311 (82 MiB / 21.4% CPU) |
4,311 (82 MiB / 21.4% CPU) |
0 (143 MiB / 21.7% CPU) |
5,171 (143 MiB / 21.7% CPU) |
4,151 (171 MiB / 44.8% CPU) |
4,151 (171 MiB / 44.8% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 4,469 (237 MiB / 44.5% CPU) |
4,469 (237 MiB / 44.5% CPU) |
1,131 (116 MiB / 24.1% CPU) |
1,131 (116 MiB / 24.1% CPU) |
Not measured | Not measured | Not measured | Not measured |
3,737 (247 MiB / 51.0% CPU) |
3,737 (247 MiB / 51.0% CPU) |
Linux nginx H1/H2/H3 POST completed (nginx.org mainline). TWP÷YARP H1 ≈ 1.5×; H2 ≈ 1.3×; H3 ≈ 1.1×. TWP÷nginx H3 ≈ 4×.

Userspace 5 ms one-way delay + 1% TCP connection stall (H1/H2) or UDP datagram drop (H3); 64 KiB GET. Median of 3 repeats on windows-latest @ 803a69bc — 34394872353 (compare-lossy).
| Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain |
663 (107 MiB / 4.7% CPU) |
663 (107 MiB / 4.7% CPU) |
635 (141 MiB / 20.3% CPU) |
635 (141 MiB / 20.3% CPU) |
Not possible | Not possible | Not possible | Not possible | 🥇 663 (116 MiB / 4.9% CPU) |
663 (116 MiB / 4.9% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain |
0 (138 MiB / 1.8% CPU) |
87 (138 MiB / 1.8% CPU) |
0 (141 MiB / 0.7% CPU) |
17 (141 MiB / 0.7% CPU) |
Not possible | Not possible | Not possible | Not possible |
0 (99 MiB / 1.0% CPU) |
16 (99 MiB / 1.0% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
0 (67 MiB / 0.0% CPU) |
0 (67 MiB / 0.0% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
0 (76 MiB / 0.0% CPU) |
0 (76 MiB / 0.0% CPU) |
TWP H2 HOL leads (~3.31× YARP). H3 is the protocol-shape win vs H2 HOL on the same lossy session; Win H3 GHA remains 0 (laptop remeasure kept above).

Median of 3 repeats @ 803a69bc. Source: 34394872353 (compare-lossy; lossy H3 uses quic-http3).
| Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain |
1,208 (144 MiB / 13.2% CPU) |
1,208 (144 MiB / 13.2% CPU) |
🥇 1,214 (99 MiB / 11.0% CPU) |
1,214 (99 MiB / 11.0% CPU) |
1,213 (83 MiB / 6.1% CPU) |
1,213 (83 MiB / 6.1% CPU) |
1,202 (127 MiB / 8.2% CPU) |
1,202 (127 MiB / 8.2% CPU) |
1,203 (154 MiB / 16.1% CPU) |
1,203 (154 MiB / 16.1% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 320 (187 MiB / 6.7% CPU) |
320 (187 MiB / 6.7% CPU) |
40 (99 MiB / 0.3% CPU) |
40 (99 MiB / 0.3% CPU) |
41 (84 MiB / 0.2% CPU) |
41 (84 MiB / 0.2% CPU) |
40 (136 MiB / 0.3% CPU) |
40 (136 MiB / 0.3% CPU) |
40 (127 MiB / 1.4% CPU) |
40 (127 MiB / 1.4% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 336 (150 MiB / 12.7% CPU) |
336 (150 MiB / 12.7% CPU) |
93 (107 MiB / 2.1% CPU) |
93 (107 MiB / 2.1% CPU) |
Not measured | Not measured | Not measured | Not measured |
336 (179 MiB / 20.4% CPU) |
336 (179 MiB / 20.4% CPU) |
TWP H2 HOL ≫ YARP (~7.7×). H3 TWP÷YARP ≈ 1×.


compare-arch isolates slow app readers, origin-early response, H2 duplex, and WebSocket echo. See TWP vs YARP IO model. Laptop 1-rep numbers are on Performance Local Lab.
Median of 3 repeats on matched 4 vCPU / 16 GiB runners @ 803a69bc (34394884256) (compare-arch). Slow consumer = 256 KiB GET, 16 KiB read + 8 ms sleep. Early response = 64 KiB POST, origin writes after 8 KiB. Duplex H2 = overlapping 64 KiB POST on H2 TLS↔H2 TLS. WebSocket = echo round-trips/sec.
compare-lossy (slow network) is already published above; it is not a slow app reader.

| Scenario | Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Slow consumer (256 KiB GET, throttled client read) | HTTP/1 · TLS | HTTP/1 · plain |
244 (101 MiB / 4.4% CPU) |
244 (101 MiB / 4.4% CPU) |
203 (143 MiB / 24.7% CPU) |
203 (143 MiB / 24.7% CPU) |
Not possible | Not possible | Not possible | Not possible | 🥇 248 (110 MiB / 4.9% CPU) |
248 (110 MiB / 4.9% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/1 · plain |
248 (117 MiB / 4.4% CPU) |
248 (117 MiB / 4.4% CPU) |
161 (141 MiB / 24.7% CPU) |
161 (141 MiB / 24.7% CPU) |
Not possible | Not possible | Not possible | Not possible | 🥇 254 (110 MiB / 6.9% CPU) |
254 (110 MiB / 6.9% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 272 (95 MiB / 18.2% CPU) |
272 (95 MiB / 18.2% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
259 (164 MiB / 21.4% CPU) |
259 (164 MiB / 21.4% CPU) |
| Early response (origin writes after first request chunk) | HTTP/1 · TLS | HTTP/1 · plain | 🥇 5,710 (99 MiB / 40.6% CPU) |
5,710 (99 MiB / 40.6% CPU) |
360 (142 MiB / 24.7% CPU) |
360 (142 MiB / 24.7% CPU) |
Not possible | Not possible | Not possible | Not possible |
3,320 (137 MiB / 47.9% CPU) |
3,320 (137 MiB / 47.9% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/1 · plain | 🥇 4,085 (187 MiB / 35.4% CPU) |
4,085 (187 MiB / 35.4% CPU) |
0 (143 MiB / 24.8% CPU) |
336 (143 MiB / 24.8% CPU) |
Not possible | Not possible | Not possible | Not possible |
2,637 (134 MiB / 39.8% CPU) |
2,637 (134 MiB / 39.8% CPU) |
| Early response (origin writes after first request chunk) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,235 (147 MiB / 41.9% CPU) |
2,235 (147 MiB / 41.9% CPU) |
Not possible (no QUIC) | Not possible (no QUIC) | Not possible | Not possible | Not possible | Not possible |
1,915 (201 MiB / 51.8% CPU) |
1,915 (201 MiB / 51.8% CPU) |
| Duplex (both directions live) | HTTP/2 · TLS | HTTP/2 · TLS |
0 (96 MiB / 0.0% CPU) |
0 (96 MiB / 0.0% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
0 (116 MiB / 0.2% CPU) |
0 (116 MiB / 0.2% CPU) |
| Duplex (WebSocket / extended CONNECT) | HTTP/1 · TLS | HTTP/1 · plain | 🥇 30,338 (96 MiB / 41.7% CPU) |
30,338 (96 MiB / 41.7% CPU) |
17,356 (142 MiB / 24.5% CPU) |
17,356 (142 MiB / 24.5% CPU) |
Not possible | Not possible | Not possible | Not possible |
28,601 (89 MiB / 44.2% CPU) |
28,601 (89 MiB / 44.2% CPU) |

| Scenario | Client | Origin | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Slow consumer (256 KiB GET, throttled client read) | HTTP/1 · TLS | HTTP/1 · plain |
466 (121 MiB / 9.6% CPU) |
466 (121 MiB / 9.6% CPU) |
415 (100 MiB / 9.9% CPU) |
415 (100 MiB / 9.9% CPU) |
🥇 472 (81 MiB / 5.6% CPU) |
472 (81 MiB / 5.6% CPU) |
471 (139 MiB / 6.2% CPU) |
471 (139 MiB / 6.2% CPU) |
424 (148 MiB / 15.1% CPU) |
424 (148 MiB / 15.1% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/1 · plain |
473 (146 MiB / 19.7% CPU) |
473 (146 MiB / 19.7% CPU) |
459 (99 MiB / 21.9% CPU) |
459 (99 MiB / 21.9% CPU) |
🥇 474 (84 MiB / 7.4% CPU) |
474 (84 MiB / 7.4% CPU) |
469 (156 MiB / 7.4% CPU) |
469 (156 MiB / 7.4% CPU) |
473 (146 MiB / 24.5% CPU) |
473 (146 MiB / 24.5% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 472 (130 MiB / 33.5% CPU) |
472 (130 MiB / 33.5% CPU) |
0 (119 MiB / 22.6% CPU) |
305 (119 MiB / 22.6% CPU) |
Not measured | Not measured | Not measured | Not measured |
470 (198 MiB / 39.5% CPU) |
470 (198 MiB / 39.5% CPU) |
| Early response (origin writes after first request chunk) | HTTP/1 · TLS | HTTP/1 · plain |
4,694 (139 MiB / 47.6% CPU) |
4,694 (139 MiB / 47.6% CPU) |
3,048 (100 MiB / 46.8% CPU) |
3,048 (100 MiB / 46.8% CPU) |
🥇 5,338 (82 MiB / 39.9% CPU) |
5,338 (82 MiB / 39.9% CPU) |
0 (129 MiB / 8.8% CPU) |
650 (129 MiB / 8.8% CPU) |
3,170 (178 MiB / 56.2% CPU) |
3,170 (178 MiB / 56.2% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/1 · plain | 🥇 3,128 (236 MiB / 46.9% CPU) |
3,128 (236 MiB / 46.9% CPU) |
0 (111 MiB / 24.1% CPU) |
1,332 (111 MiB / 24.1% CPU) |
2,125 (83 MiB / 23.7% CPU) |
2,125 (83 MiB / 23.7% CPU) |
0 (147 MiB / 23.4% CPU) |
2,522 (147 MiB / 23.4% CPU) |
2,262 (170 MiB / 49.1% CPU) |
2,262 (170 MiB / 49.1% CPU) |
| Early response (origin writes after first request chunk) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,873 (203 MiB / 44.9% CPU) |
2,873 (203 MiB / 44.9% CPU) |
0 (116 MiB / 24.8% CPU) |
442 (116 MiB / 24.8% CPU) |
Not measured | Not measured | Not measured | Not measured |
2,110 (260 MiB / 48.0% CPU) |
2,110 (260 MiB / 48.0% CPU) |
| Duplex (both directions live) | HTTP/2 · TLS | HTTP/2 · TLS |
0 (100 MiB / 0.1% CPU) |
0 (100 MiB / 0.1% CPU) |
Not possible | Not possible | Not possible | Not possible | Not possible | Not possible |
0 (149 MiB / 0.3% CPU) |
0 (149 MiB / 0.3% CPU) |
| Duplex (WebSocket / extended CONNECT) | HTTP/1 · TLS | HTTP/1 · plain |
30,840 (126 MiB / 44.0% CPU) |
30,840 (126 MiB / 44.0% CPU) |
🥇 33,431 (99 MiB / 35.5% CPU) |
33,431 (99 MiB / 35.5% CPU) |
32,680 (81 MiB / 38.3% CPU) |
32,680 (81 MiB / 38.3% CPU) |
0 (127 MiB / 40.5% CPU) |
0 (127 MiB / 40.5% CPU) |
27,895 (125 MiB / 44.3% CPU) |
27,895 (125 MiB / 44.3% CPU) |
Slow consumer is sleep-bound; H1/H2/H3 sit in the same band. Early-response H1/H2/H3: TWP leads (H1 early ≈ 2.00× / 1.47× YARP Win/Linux). Duplex H2: YARP leads by design — Win ≈ 0.59× (1,270 / 2,135), Linux ≈ 0.15× (282 / 1,882); irreducible concurrent-copier cell (see IO model). WebSocket: TWP÷YARP Windows ≈ 1.06×; Linux nginx leads.
Isolates keep-alive tiny GET vs new connection per request (handshake-dominated) vs keep-alive 256 KiB. Product comparison uses RPS and end-to-end latency; TWP can also capture ClientTlsTiming when TWP_RPS_CAPTURE_TLS=1 (child process) — nginx/YARP have no equivalent hook.


Median of 3 repeats on windows-latest @ 803a69bc. Source: Actions 34394878841 (compare-tls-cost). Absolute RPS on GHA swings hard; prefer TWP÷YARP.

| Workload | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|
| Keep-alive · tiny GET | 🥇 19,208 (88 MiB / 48.1% CPU) |
19,208 (88 MiB / 48.1% CPU) |
8,700 (139 MiB / 24.9% CPU) |
8,700 (139 MiB / 24.9% CPU) |
Not possible | Not possible | Not possible | Not possible |
17,590 (102 MiB / 51.3% CPU) |
17,590 (102 MiB / 51.3% CPU) |
| New-connection · tiny GET | 🥇 702 (89 MiB / 9.1% CPU) |
702 (89 MiB / 9.1% CPU) |
0 (140 MiB / 24.2% CPU) |
239 (140 MiB / 24.2% CPU) |
Not possible | Not possible | Not possible | Not possible |
683 (115 MiB / 10.5% CPU) |
683 (115 MiB / 10.5% CPU) |
| Keep-alive · 256 KiB GET | 🥇 2,710 (124 MiB / 44.6% CPU) |
2,710 (124 MiB / 44.6% CPU) |
0 (142 MiB / 24.6% CPU) |
148 (142 MiB / 24.6% CPU) |
Not possible | Not possible | Not possible | Not possible |
2,583 (133 MiB / 47.2% CPU) |
2,583 (133 MiB / 47.2% CPU) |
Median of 3 repeats @ 803a69bc. Source: Actions 34394878841 (compare-tls-cost).

| Workload | TWP sustain | TWP peak | nginx sustain | nginx peak | HAProxy sustain | HAProxy peak | Envoy sustain | Envoy peak | YARP sustain | YARP peak |
|---|---|---|---|---|---|---|---|---|---|---|
| Keep-alive · tiny GET |
23,413 (110 MiB / 50.0% CPU) |
23,413 (110 MiB / 50.0% CPU) |
27,894 (99 MiB / 42.0% CPU) |
27,894 (99 MiB / 42.0% CPU) |
🥇 29,008 (83 MiB / 42.7% CPU) |
29,008 (83 MiB / 42.7% CPU) |
16,871 (126 MiB / 59.3% CPU) |
16,871 (126 MiB / 59.3% CPU) |
20,839 (137 MiB / 51.3% CPU) |
20,839 (137 MiB / 51.3% CPU) |
| New-connection · tiny GET |
973 (123 MiB / 47.6% CPU) |
973 (123 MiB / 47.6% CPU) |
1,017 (100 MiB / 44.6% CPU) |
1,017 (100 MiB / 44.6% CPU) |
943 (81 MiB / 44.7% CPU) |
943 (81 MiB / 44.7% CPU) |
🥇 1,070 (129 MiB / 41.5% CPU) |
1,070 (129 MiB / 41.5% CPU) |
962 (151 MiB / 46.4% CPU) |
962 (151 MiB / 46.4% CPU) |
| Keep-alive · 256 KiB GET |
2,681 (127 MiB / 36.9% CPU) |
2,681 (127 MiB / 36.9% CPU) |
1,784 (98 MiB / 53.8% CPU) |
1,784 (98 MiB / 53.8% CPU) |
🥇 3,014 (82 MiB / 32.8% CPU) |
3,014 (82 MiB / 32.8% CPU) |
2,707 (145 MiB / 34.2% CPU) |
2,707 (145 MiB / 34.2% CPU) |
2,103 (165 MiB / 45.8% CPU) |
2,103 (165 MiB / 45.8% CPU) |
All three workloads are >1.00× YARP on both OS. nginx leads Linux keep-alive tiny and Linux new-connection; TWP is second, YARP third.
| What | Result |
|---|---|
| HTTPS TTFB vs direct (median, 14 hosts) | Cold ≈ parity (−1 ms); warm −25 ms (proxy faster) |
| HTTP/1 loopback GET (no body intercept) | ~128 µs, ~9.9 KB allocated / request |
| Basic example footprint (Release, after load) | ~74 MB working set · ~24–29 MB private bytes |
dotnet run -c Release --project benchmarks/Titanium.Web.Proxy.Benchmarks -- --filter '*Throughput*'Local laptop BDN @ 9d7c2966 (Release):
| Benchmark | Setup | Mean | Allocated / op |
|---|---|---|---|
| HTTP/1 GET through proxy | Passthrough | 128 µs | 9.9 KB |
| HTTP/2 multiplexed GETs | 10 concurrent streams | 253 µs / batch | ~4.4 KB / request |
There is no artificial upper clamp on server defaults. Per-endpoint overrides:
| Knob | Scope | Default | Override |
|---|---|---|---|
ProxyServer.MaxCachedConnections |
process, per upstream host | 128 | any ≥ 1 |
ProxyEndPoint.MaxCachedConnections |
endpoint → pool depth for that EP’s sessions | null (use server) | e.g. 256 on reverse EP |
ProxyEndPoint.MaxConcurrentClients |
endpoint admission | null (off) | any ≥ 1 |
ResourceLimits.MaxConcurrentStreamsPerConnection |
H2 streams | 256 | ProxyResourceLimits.Create(...) |
TransparentQuicProxyEndPoint.MaxInboundBidirectionalStreams |
H3 | 100 (probe uses 256) | property on EP |
ForwardCleartext |
transparent TLS terminate | false |
true + decrypt |
proxy.MaxCachedConnections = 512;
proxy.ResourceLimits = ProxyResourceLimits.Create(
/* … */,
maxConcurrentStreamsPerConnection: 1000,
maxCachedConnectionsPerHost: 512,
/* … */);
var ep = new TransparentProxyEndPoint(IPAddress.Any, 443, decryptSsl: true)
{
ForwardHost = "127.0.0.1",
ForwardPort = 8080,
ForwardCleartext = true,
MaxCachedConnections = 256,
GenericCertificateName = "example.com"
};
ep.BeforeSslAuthenticate += (_, a) =>
{
a.UpstreamHttpProtocol = UpstreamHttpProtocol.Http11;
a.AllowHttpProtocolTranslation = true; // HTTP/2 client → HTTP/1 origin
return Task.CompletedTask;
};