Repository navigation
Performance
Throughput and footprint of Titanium as a reverse / edge proxy and as a decrypting (MITM) proxy, measured on the same harness against YARP, nginx, HAProxy, and Envoy where each OS can run them.
RPS is requests per second (gRPC tables use RPC/s). Numbers are Release builds on matched GitHub-hosted runners: Windows and Linux at 4 vCPU / 16 GiB, macOS at macos-15-intel 4-core / 14 GB. Read within one table — absolute RPS is not comparable across operating systems. Not possible means that product cannot run that path on that OS; Not measured means the path exists but no published number yet.
For pooling knobs and certificate first-visit tuning, see Performance and pooling. Laptop cool A/B tables (not publishable) live on Performance Local Lab.
- Same load generator, same origin process, and the same warmup / measure windows (2s / 8s) with the same concurrency ramp (8, 16, 32, 64).
- Every reverse arm is three OS processes: load generator + origin + proxy. 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). Laptop numbers are never mixed into these tables. - Peers use equivalent TLS/ALPN and streaming-friendly settings on the same loopback shape. HAProxy and Envoy are Linux/macOS only — Not possible on Windows, so their columns are omitted there.
- MITM (HTTPS decryption with forged certificates) is Titanium-only; peers cannot MITM. Those tables show Titanium MITM overhead versus its own reverse path on the same wires.
- Tiny keep-alive GET (~56-byte JSON) is the industry RPS shape (same class as wrk / TechEmpower). It is also real for small JSON APIs and health checks.
- Same-protocol H2↔H2 / H3↔H3 on that shape is Titanium’s best case: with interception off, Titanium copies frames instead of decoding and re-encoding headers (peers do a full HTTP decode). Medals there are not the typical reverse-proxy job.
- Typical reverse is H1 TLS→H1 or H2→H1 (~1.1× YARP on tiny GET). With larger bodies, see Heavier reverse: at 64 KiB H2 TLS→H2 TLS, Titanium is behind YARP (~0.69–0.76×).
- Bold RPS is sustain (last concurrency that still met error/latency SLOs). When peak differs, it appears in the same cell as
<sub>(peak N · …)</sub>with RSS/CPU. When sustain equals peak, peak is omitted. - 🥇 = best among Titanium / nginx / YARP on Reverse rows (highest RPS; on a tie, lower memory then lower CPU%). Gold medals are per cell on tiny GET — an H2↔H2 gold is that frame-copy best case, not “Titanium is 1.7× on all reverse.” For larger-body H2→H2, see the heavier tables.
- MITM tables are Titanium-only. Lite÷Reverse / Full÷Reverse = Titanium MITM sustain ÷ Titanium reverse sustain on the same Client×Origin pair — the overhead of decrypting and intercepting versus bare reverse, not versus nginx or YARP.
- Not possible = cannot do that path. Not measured = path exists but no published number yet. When every row would be Not possible for a peer, that column is omitted and a note above the table explains why.
- Why this comparison is fair
- How to read the tables
- Measurement environment
- Windows — Titanium vs nginx 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
- Unary gRPC (H2 TLS)
- Unary gRPC (H2 TLS → h2c)
- WebSocket (H1 TLS → H1 TLS)
- WebSocket (H2 TLS 8441 → H1)
- Other measurements
- Raising limits on large hosts
- Maintainer notes
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: warmup 2s / measure 8s; concurrency 8, 16, 32, 64; median of 3 repeats. Prefer Titanium÷YARP / Titanium÷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.
| 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 @ 8bfa7852 — 35092854050. 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).
| 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 | RPS | % of origin-HttpClient |
|---|---|---|---|
| origin-direct | dotnet-httpclient |
81,217 (55 MiB / 45.3% CPU) |
100.0% |
| origin-direct-bombardier | bombardier |
61,721 (56 MiB / 25.1% CPU) |
76.0% |
| bare-reverse-http1 | dotnet-httpclient |
39,529 (56 MiB / 44.1% CPU) |
48.7% |
| nginx-reverse-http1 | dotnet-httpclient |
24,677 (125 MiB / 24.9% CPU) |
30.4% |
| yarp-reverse-http1 | dotnet-httpclient |
34,602 (90 MiB / 49.2% CPU) |
42.6% |
| twp-reverse-http1 | dotnet-httpclient | 🥇 40,685 (76 MiB / 50.9% CPU) |
50.1% |
Linux (ubuntu-latest)
| Arm | Generator | RPS | % of origin-HttpClient |
|---|---|---|---|
| origin-direct | dotnet-httpclient |
131,605 (81 MiB / 45.0% CPU) |
100.0% |
| origin-direct-bombardier | bombardier |
81,228 (80 MiB / 38.3% CPU) |
61.7% |
| bare-reverse-http1 | dotnet-httpclient |
58,771 (70 MiB / 46.0% CPU) |
44.7% |
| nginx-reverse-http1 | dotnet-httpclient | 🥇 74,446 (77 MiB / 39.5% CPU) |
56.6% |
| yarp-reverse-http1 | dotnet-httpclient |
55,568 (115 MiB / 48.0% CPU) |
42.2% |
| twp-reverse-http1 | dotnet-httpclient |
63,570 (92 MiB / 50.3% CPU) |
48.3% |
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 | RPS | ÷YARP | ÷nginx |
|---|---|---|---|---|
| nginx-reverse-http2 | dotnet-httpclient |
14,697 (141 MiB / 24.4% CPU) |
0.32× | 1.00× |
| yarp-reverse-http2 | dotnet-httpclient |
45,876 (96 MiB / 50.8% CPU) |
1.00× | 3.12× |
| twp-reverse-http2-cleartext | dotnet-httpclient | 🥇 51,822 (103 MiB / 52.9% CPU) |
1.13× | 3.53× |
Linux (ubuntu-latest)
| Arm | Generator | RPS | ÷YARP | ÷nginx |
|---|---|---|---|---|
| nginx-reverse-http2 | dotnet-httpclient |
29,423 (102 MiB / 18.9% CPU) |
0.46× | 1.00× |
| yarp-reverse-http2 | dotnet-httpclient |
64,174 (123 MiB / 46.6% CPU) |
1.00× | 2.18× |
| twp-reverse-http2-cleartext | dotnet-httpclient | 🥇 66,465 (123 MiB / 51.4% CPU) |
1.04× | 2.26× |
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 | RPS | ÷YARP | ÷nginx |
|---|---|---|---|---|
| nginx-reverse-http3-cleartext | dotnet-httpclient | Not possible (no QUIC) | — | — |
| yarp-reverse-http3-cleartext | dotnet-httpclient |
24,601 (148 MiB / 50.5% CPU) |
1.00× | — |
| twp-reverse-http3-cleartext | dotnet-httpclient | 🥇 24,701 (117 MiB / 43.9% CPU) |
1.00× | — |
Linux (ubuntu-latest)
| Arm | Generator | RPS | ÷YARP | ÷nginx |
|---|---|---|---|---|
| nginx-reverse-http3-cleartext | dotnet-httpclient |
0 (peak 38,114 · 113 MiB / 20.9% CPU) |
0.96× | 1.00× |
| yarp-reverse-http3-cleartext | dotnet-httpclient |
39,807 (215 MiB / 48.7% CPU) |
1.00× | 1.04× |
| twp-reverse-http3-cleartext | dotnet-httpclient | 🥇 44,892 (177 MiB / 53.4% CPU) |
1.13× | 1.18× |
Client / 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 @ 8bfa7852 — compare-product 35092827644. 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. Product 5×5 is ~56-byte JSON keep-alive GET; H2/H3 same-protocol cells are mostly header work with a tiny body (Titanium best case) — see Why this comparison is fair.
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 | nginx | YARP |
|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain | 🥇 31,552 (75 MiB / 51.1% CPU) |
19,301 (125 MiB / 25% CPU) |
26,979 (90 MiB / 48.1% CPU) |
| HTTP/1 · plain | HTTP/1 · TLS | 🥇 20,447 (90 MiB / 52.6% CPU) |
8,419 (136 MiB / 24.6% CPU) |
18,937 (100 MiB / 48.3% CPU) |
| HTTP/1 · plain | HTTP/2 · plain | 🥇 36,987 (108 MiB / 45.2% CPU) |
Not possible (no H2 upstream) |
32,831 (93 MiB / 46.7% CPU) |
| HTTP/1 · plain | HTTP/2 · TLS | 🥇 39,580 (117 MiB / 47.3% CPU) |
Not possible (no H2 upstream) |
36,173 (98 MiB / 48.9% CPU) |
| HTTP/1 · plain | HTTP/3 · QUIC |
11,519 (104 MiB / 35% CPU) |
Not possible (no H3 upstream) | 🥇 18,177 (118 MiB / 49.9% CPU) |
| HTTP/1 · TLS | HTTP/1 · plain | 🥇 20,182 (86 MiB / 53.9% CPU) |
8,534 (142 MiB / 24.7% CPU) |
18,049 (102 MiB / 49.4% CPU) |
| HTTP/1 · TLS | HTTP/1 · TLS | 🥇 23,518 (84 MiB / 47.8% CPU) |
9,376 (144 MiB / 24.8% CPU) |
20,816 (102 MiB / 51.2% CPU) |
| HTTP/1 · TLS | HTTP/2 · plain | 🥇 27,742 (115 MiB / 46% CPU) |
Not possible (no H2 upstream) |
26,111 (108 MiB / 49.6% CPU) |
| HTTP/1 · TLS | HTTP/2 · TLS | 🥇 25,046 (119 MiB / 47% CPU) |
Not possible (no H2 upstream) |
24,356 (108 MiB / 47.4% CPU) |
| HTTP/1 · TLS | HTTP/3 · QUIC | 🥇 19,385 (111 MiB / 50.9% CPU) |
Not possible (no H3 upstream) |
18,399 (129 MiB / 49.7% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 34,785 (95 MiB / 53.6% CPU) |
9,225 (127 MiB / 24.8% CPU) |
31,535 (84 MiB / 53.4% CPU) |
| HTTP/2 · plain | HTTP/1 · TLS | 🥇 29,745 (98 MiB / 49% CPU) |
6,559 (138 MiB / 24.9% CPU) |
27,120 (96 MiB / 54% CPU) |
| HTTP/2 · plain | HTTP/2 · plain | 🥇 114,586 (59 MiB / 27.4% CPU) |
Not possible (no H2 upstream) |
69,424 (94 MiB / 48.1% CPU) |
| HTTP/2 · plain | HTTP/2 · TLS | 🥇 86,454 (65 MiB / 28.5% CPU) |
Not possible (no H2 upstream) |
55,488 (99 MiB / 46.8% CPU) |
| HTTP/2 · plain | HTTP/3 · QUIC | 🥇 31,645 (121 MiB / 52.4% CPU) |
Not possible (no H3 upstream) |
28,884 (120 MiB / 52.8% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 39,258 (103 MiB / 51.6% CPU) |
11,247 (141 MiB / 24.3% CPU) |
35,070 (94 MiB / 49% CPU) |
| HTTP/2 · TLS | HTTP/1 · TLS | 🥇 29,467 (106 MiB / 54% CPU) |
6,379 (145 MiB / 24.6% CPU) |
25,223 (97 MiB / 51.8% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 101,094 (78 MiB / 26.5% CPU) |
Not possible (no H2 upstream) |
54,554 (99 MiB / 50.8% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 88,782 (75 MiB / 28.5% CPU) |
Not possible (no H2 upstream) |
53,218 (103 MiB / 48.9% CPU) |
| HTTP/2 · TLS | HTTP/3 · QUIC | 🥇 30,154 (129 MiB / 54.2% CPU) |
Not possible (no H3 upstream) |
26,201 (123 MiB / 51.9% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 14,399 (101 MiB / 45.8% CPU) |
Not possible (no QUIC) |
14,330 (142 MiB / 51.1% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 17,592 (116 MiB / 44.1% CPU) |
Not possible (no QUIC) |
15,607 (147 MiB / 48.8% CPU) |
| HTTP/3 · QUIC | HTTP/2 · plain | 🥇 34,116 (136 MiB / 47.7% CPU) |
Not possible (no H2 upstream) |
23,176 (151 MiB / 47.4% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 31,135 (137 MiB / 47.1% CPU) |
Not possible (no H2 upstream) |
21,466 (150 MiB / 48.8% CPU) |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
17,411 (119 MiB / 41.5% CPU) |
Not possible (no H3 upstream) | 🥇 18,619 (163 MiB / 48.3% CPU) |
Same Client×Origin wires with interception on (compare-product 35092827644). 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 / comparison-group shard). 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. @ 8bfa7852: 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 |
30,988 (80 MiB / 46.4% CPU) |
30,492 (82 MiB / 48.2% CPU) |
0.98× | 0.97× |
| HTTP/1 · plain | HTTP/1 · TLS |
20,384 (95 MiB / 52.8% CPU) |
20,198 (95 MiB / 52.5% CPU) |
1× | 0.99× |
| HTTP/1 · plain | HTTP/2 · plain |
35,601 (110 MiB / 48% CPU) |
35,122 (105 MiB / 49.1% CPU) |
0.96× | 0.95× |
| HTTP/1 · plain | HTTP/2 · TLS |
39,383 (120 MiB / 48.3% CPU) |
38,670 (120 MiB / 47.9% CPU) |
1× | 0.98× |
| HTTP/1 · plain | HTTP/3 · QUIC |
18,054 (103 MiB / 53.2% CPU) |
17,685 (107 MiB / 52.2% CPU) |
1.57× | 1.54× |
| HTTP/1 · TLS | HTTP/1 · plain |
19,722 (94 MiB / 51.6% CPU) |
19,661 (96 MiB / 51.5% CPU) |
0.98× | 0.97× |
| HTTP/1 · TLS | HTTP/1 · TLS |
23,590 (95 MiB / 48.8% CPU) |
23,182 (96 MiB / 46.8% CPU) |
1× | 0.99× |
| HTTP/1 · TLS | HTTP/2 · plain |
27,300 (110 MiB / 48.4% CPU) |
26,872 (112 MiB / 47.3% CPU) |
0.98× | 0.97× |
| HTTP/1 · TLS | HTTP/2 · TLS |
25,107 (124 MiB / 45.4% CPU) |
24,748 (127 MiB / 47.5% CPU) |
1× | 0.99× |
| HTTP/1 · TLS | HTTP/3 · QUIC |
18,795 (112 MiB / 50.6% CPU) |
18,674 (115 MiB / 53.7% CPU) |
0.97× | 0.96× |
| HTTP/2 · plain | HTTP/1 · plain |
33,811 (97 MiB / 55.8% CPU) |
33,355 (100 MiB / 55.9% CPU) |
0.97× | 0.96× |
| HTTP/2 · plain | HTTP/1 · TLS |
29,166 (105 MiB / 53.2% CPU) |
28,519 (103 MiB / 55.1% CPU) |
0.98× | 0.96× |
| HTTP/2 · plain | HTTP/2 · plain |
90,897 (69 MiB / 41.3% CPU) |
84,998 (70 MiB / 43% CPU) |
0.79× | 0.74× |
| HTTP/2 · plain | HTTP/2 · TLS |
70,465 (78 MiB / 38.9% CPU) |
66,829 (79 MiB / 38.1% CPU) |
0.82× | 0.77× |
| HTTP/2 · plain | HTTP/3 · QUIC |
31,297 (116 MiB / 54.3% CPU) |
30,424 (115 MiB / 55.6% CPU) |
0.99× | 0.96× |
| HTTP/2 · TLS | HTTP/1 · plain |
38,437 (103 MiB / 53.4% CPU) |
37,561 (112 MiB / 51.9% CPU) |
0.98× | 0.96× |
| HTTP/2 · TLS | HTTP/1 · TLS |
28,202 (105 MiB / 55.9% CPU) |
27,538 (102 MiB / 54.8% CPU) |
0.96× | 0.93× |
| HTTP/2 · TLS | HTTP/2 · plain |
79,360 (87 MiB / 40.9% CPU) |
74,807 (87 MiB / 40.4% CPU) |
0.79× | 0.74× |
| HTTP/2 · TLS | HTTP/2 · TLS |
74,209 (81 MiB / 39.1% CPU) |
70,572 (85 MiB / 39.3% CPU) |
0.84× | 0.79× |
| HTTP/2 · TLS | HTTP/3 · QUIC |
30,269 (131 MiB / 53.9% CPU) |
29,544 (130 MiB / 52.9% CPU) |
1× | 0.98× |
| HTTP/3 · QUIC | HTTP/1 · plain |
13,942 (113 MiB / 45.5% CPU) |
13,712 (111 MiB / 46.6% CPU) |
0.97× | 0.95× |
| HTTP/3 · QUIC | HTTP/1 · TLS |
16,217 (120 MiB / 44% CPU) |
15,113 (124 MiB / 44.8% CPU) |
0.92× | 0.86× |
| HTTP/3 · QUIC | HTTP/2 · plain |
31,662 (139 MiB / 51.8% CPU) |
29,770 (139 MiB / 50.7% CPU) |
0.93× | 0.87× |
| HTTP/3 · QUIC | HTTP/2 · TLS |
28,483 (130 MiB / 49.9% CPU) |
27,720 (133 MiB / 48.2% CPU) |
0.91× | 0.89× |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
17,584 (123 MiB / 45.2% CPU) |
23,082 (123 MiB / 46.4% CPU) |
1.01× | 1.33× |
Median of 3 repeats on ubuntu-latest (4 vCPU / 16 GiB). Bare reverse 5×5 @ 8bfa7852 — compare-product 35092827644. 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. Product 5×5 is ~56-byte JSON keep-alive GET; H2/H3 same-protocol cells are mostly header work with a tiny body (Titanium best case) — see Why this comparison is fair.
| Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain |
52,118 (95 MiB / 48.8% CPU) |
🥇 70,158 (76 MiB / 38.3% CPU) |
66,062 (64 MiB / 41.8% CPU) |
38,126 (116 MiB / 59.3% CPU) |
47,079 (115 MiB / 49.4% CPU) |
| HTTP/1 · plain | HTTP/1 · TLS |
23,523 (107 MiB / 51.1% CPU) |
28,019 (92 MiB / 42.1% CPU) |
🥇 28,193 (68 MiB / 43.6% CPU) |
17,450 (118 MiB / 59% CPU) |
21,545 (127 MiB / 50.3% CPU) |
| HTTP/1 · plain | HTTP/2 · plain | 🥇 40,142 (129 MiB / 51.1% CPU) |
Not possible (no H2 upstream) |
26,857 (65 MiB / 42.4% CPU) |
22,067 (116 MiB / 63.3% CPU) |
35,268 (126 MiB / 48.8% CPU) |
| HTTP/1 · plain | HTTP/2 · TLS |
51,676 (158 MiB / 48.4% CPU) |
Not possible (no H2 upstream) | 🥇 54,114 (68 MiB / 41% CPU) |
37,450 (117 MiB / 57.4% CPU) |
48,422 (131 MiB / 48.2% CPU) |
| HTTP/1 · plain | HTTP/3 · QUIC | 🥇 23,529 (143 MiB / 53.6% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
10,746 (120 MiB / 56.1% CPU) |
21,315 (156 MiB / 49.2% CPU) |
| HTTP/1 · TLS | HTTP/1 · plain |
22,984 (112 MiB / 49.8% CPU) |
🥇 27,706 (100 MiB / 41.6% CPU) |
27,584 (83 MiB / 43.2% CPU) |
17,122 (127 MiB / 58% CPU) |
20,033 (134 MiB / 50.2% CPU) |
| HTTP/1 · TLS | HTTP/1 · TLS |
35,195 (112 MiB / 45.8% CPU) |
43,625 (105 MiB / 38.9% CPU) |
🥇 44,407 (84 MiB / 40.7% CPU) |
30,389 (127 MiB / 53.8% CPU) |
31,410 (143 MiB / 48.3% CPU) |
| HTTP/1 · TLS | HTTP/2 · plain | 🥇 27,813 (141 MiB / 50.6% CPU) |
Not possible (no H2 upstream) |
21,585 (84 MiB / 42.3% CPU) |
18,519 (126 MiB / 58.2% CPU) |
25,000 (148 MiB / 49.7% CPU) |
| HTTP/1 · TLS | HTTP/2 · TLS | 🥇 24,173 (155 MiB / 48.7% CPU) |
Not possible (no H2 upstream) |
23,923 (84 MiB / 43.3% CPU) |
17,323 (126 MiB / 57.5% CPU) |
22,008 (148 MiB / 47.6% CPU) |
| HTTP/1 · TLS | HTTP/3 · QUIC | 🥇 26,290 (157 MiB / 51.9% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
17,802 (130 MiB / 52.4% CPU) |
24,262 (171 MiB / 49.8% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 36,694 (120 MiB / 53.3% CPU) |
16,440 (79 MiB / 19% CPU) |
23,041 (66 MiB / 24.6% CPU) |
13,601 (118 MiB / 23.2% CPU) |
34,322 (114 MiB / 50.2% CPU) |
| HTTP/2 · plain | HTTP/1 · TLS | 🥇 27,336 (129 MiB / 51% CPU) |
12,859 (100 MiB / 19.6% CPU) |
18,613 (70 MiB / 24.6% CPU) |
12,985 (119 MiB / 23.4% CPU) |
25,795 (127 MiB / 51% CPU) |
| HTTP/2 · plain | HTTP/2 · plain | 🥇 118,021 (89 MiB / 36% CPU) |
Not possible (no H2 upstream) |
48,169 (68 MiB / 24.2% CPU) |
25,730 (116 MiB / 21.3% CPU) |
68,950 (137 MiB / 48% CPU) |
| HTTP/2 · plain | HTTP/2 · TLS | 🥇 58,227 (86 MiB / 35.1% CPU) |
Not possible (no H2 upstream) |
19,761 (69 MiB / 24.6% CPU) |
15,929 (118 MiB / 21.6% CPU) |
39,519 (128 MiB / 45.6% CPU) |
| HTTP/2 · plain | HTTP/3 · QUIC | 🥇 27,742 (147 MiB / 50.5% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
4,280 (121 MiB / 24.9% CPU) |
26,149 (153 MiB / 46.4% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 49,222 (125 MiB / 49.5% CPU) |
30,623 (102 MiB / 19.1% CPU) |
40,300 (81 MiB / 24.3% CPU) |
22,680 (129 MiB / 22.7% CPU) |
45,846 (123 MiB / 47.7% CPU) |
| HTTP/2 · TLS | HTTP/1 · TLS | 🥇 26,240 (122 MiB / 50.9% CPU) |
12,497 (110 MiB / 19.9% CPU) |
16,768 (85 MiB / 24.4% CPU) |
13,048 (128 MiB / 23.3% CPU) |
23,132 (133 MiB / 50.3% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 75,608 (99 MiB / 36.6% CPU) |
Not possible (no H2 upstream) |
21,385 (82 MiB / 24.4% CPU) |
17,016 (126 MiB / 22.1% CPU) |
39,430 (130 MiB / 47.6% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 81,336 (101 MiB / 34.1% CPU) |
Not possible (no H2 upstream) |
38,308 (82 MiB / 24.2% CPU) |
24,040 (126 MiB / 21.6% CPU) |
46,396 (129 MiB / 44% CPU) |
| HTTP/2 · TLS | HTTP/3 · QUIC | 🥇 25,960 (154 MiB / 50.3% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
4,339 (129 MiB / 24.8% CPU) |
22,717 (154 MiB / 47% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
20,471 (147 MiB / 50% CPU) |
0 (peak 15,362 · 106 MiB / 22.5% CPU) |
🥇 23,011 (87 MiB / 26.3% CPU) |
3,989 (136 MiB / 24.8% CPU) |
18,716 (183 MiB / 50.2% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
23,817 (165 MiB / 45.2% CPU) |
0 (peak 27,931 · 123 MiB / 20.1% CPU) |
🥇 29,255 (91 MiB / 25.8% CPU) |
14 (136 MiB / 0.1% CPU) |
22,334 (203 MiB / 47.7% CPU) |
| HTTP/3 · QUIC | HTTP/2 · plain | 🥇 28,969 (158 MiB / 53% CPU) |
Not possible (no H2 upstream) |
24,863 (86 MiB / 25.8% CPU) |
26 (130 MiB / 0.2% CPU) |
23,885 (197 MiB / 48.2% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 25,108 (157 MiB / 50.6% CPU) |
Not possible (no H2 upstream) |
20,697 (88 MiB / 25.4% CPU) |
1,471 (132 MiB / 7.8% CPU) |
21,179 (196 MiB / 47.3% CPU) |
| HTTP/3 · QUIC | HTTP/3 · QUIC | 🥇 22,764 (170 MiB / 44.9% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
448 (130 MiB / 2.2% CPU) |
19,268 (210 MiB / 47.5% CPU) |
Same Client×Origin wires with interception on (compare-product 35092827644). 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 / comparison-group shard). 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. @ 8bfa7852: 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 |
51,441 (97 MiB / 50.2% CPU) |
50,516 (98 MiB / 49.8% CPU) |
0.99× | 0.97× |
| HTTP/1 · plain | HTTP/1 · TLS |
23,548 (112 MiB / 52.2% CPU) |
23,482 (113 MiB / 51.3% CPU) |
1× | 1× |
| HTTP/1 · plain | HTTP/2 · plain |
40,450 (132 MiB / 53.8% CPU) |
38,305 (126 MiB / 53% CPU) |
1.01× | 0.95× |
| HTTP/1 · plain | HTTP/2 · TLS |
50,652 (153 MiB / 50.5% CPU) |
49,728 (154 MiB / 50.8% CPU) |
0.98× | 0.96× |
| HTTP/1 · plain | HTTP/3 · QUIC |
22,774 (142 MiB / 54.2% CPU) |
22,822 (137 MiB / 54.3% CPU) |
0.97× | 0.97× |
| HTTP/1 · TLS | HTTP/1 · plain |
23,114 (115 MiB / 50.9% CPU) |
22,808 (116 MiB / 50.3% CPU) |
1.01× | 0.99× |
| HTTP/1 · TLS | HTTP/1 · TLS |
34,966 (117 MiB / 46.7% CPU) |
34,270 (118 MiB / 46.8% CPU) |
0.99× | 0.97× |
| HTTP/1 · TLS | HTTP/2 · plain |
27,488 (143 MiB / 51.3% CPU) |
26,553 (148 MiB / 51.2% CPU) |
0.99× | 0.95× |
| HTTP/1 · TLS | HTTP/2 · TLS |
23,559 (170 MiB / 48.9% CPU) |
23,089 (169 MiB / 48.7% CPU) |
0.97× | 0.96× |
| HTTP/1 · TLS | HTTP/3 · QUIC |
25,937 (156 MiB / 52.5% CPU) |
26,697 (161 MiB / 53.2% CPU) |
0.99× | 1.02× |
| HTTP/2 · plain | HTTP/1 · plain |
36,272 (120 MiB / 54.6% CPU) |
34,343 (120 MiB / 54.4% CPU) |
0.99× | 0.94× |
| HTTP/2 · plain | HTTP/1 · TLS |
27,164 (124 MiB / 52.4% CPU) |
26,445 (129 MiB / 52.2% CPU) |
0.99× | 0.97× |
| HTTP/2 · plain | HTTP/2 · plain |
88,034 (101 MiB / 40.5% CPU) |
81,934 (98 MiB / 40.2% CPU) |
0.75× | 0.69× |
| HTTP/2 · plain | HTTP/2 · TLS |
48,275 (95 MiB / 40.2% CPU) |
45,097 (98 MiB / 40.2% CPU) |
0.83× | 0.77× |
| HTTP/2 · plain | HTTP/3 · QUIC |
27,595 (143 MiB / 50.5% CPU) |
27,350 (147 MiB / 51.1% CPU) |
0.99× | 0.99× |
| HTTP/2 · TLS | HTTP/1 · plain |
48,535 (132 MiB / 50.6% CPU) |
46,791 (134 MiB / 50% CPU) |
0.99× | 0.95× |
| HTTP/2 · TLS | HTTP/1 · TLS |
25,779 (123 MiB / 51.8% CPU) |
25,125 (129 MiB / 52.1% CPU) |
0.98× | 0.96× |
| HTTP/2 · TLS | HTTP/2 · plain |
55,993 (110 MiB / 41.3% CPU) |
53,218 (114 MiB / 41% CPU) |
0.74× | 0.7× |
| HTTP/2 · TLS | HTTP/2 · TLS |
64,140 (113 MiB / 36.2% CPU) |
60,738 (112 MiB / 36.2% CPU) |
0.79× | 0.75× |
| HTTP/2 · TLS | HTTP/3 · QUIC |
26,305 (160 MiB / 51.5% CPU) |
25,709 (152 MiB / 50.6% CPU) |
1.01× | 0.99× |
| HTTP/3 · QUIC | HTTP/1 · plain |
19,415 (144 MiB / 50.9% CPU) |
19,586 (145 MiB / 50.1% CPU) |
0.95× | 0.96× |
| HTTP/3 · QUIC | HTTP/1 · TLS |
23,253 (171 MiB / 47.3% CPU) |
22,767 (170 MiB / 46% CPU) |
0.98× | 0.96× |
| HTTP/3 · QUIC | HTTP/2 · plain |
27,212 (160 MiB / 54.2% CPU) |
26,934 (163 MiB / 54.5% CPU) |
0.94× | 0.93× |
| HTTP/3 · QUIC | HTTP/2 · TLS |
23,551 (152 MiB / 51.9% CPU) |
22,624 (153 MiB / 51.9% CPU) |
0.94× | 0.9× |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
21,049 (171 MiB / 48.3% CPU) |
20,830 (171 MiB / 48.2% CPU) |
0.92× | 0.92× |
Median of 3 repeats on macos-15-intel (4-core / 14 GB). Bare reverse 5×5 @ 8bfa7852 — compare-product 35092827644. 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). Product 5×5 is ~56-byte JSON keep-alive GET; H2/H3 same-protocol cells are mostly header work with a tiny body (Titanium best case) — see Why this comparison is fair.
| Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|
| HTTP/1 · plain | HTTP/1 · plain | 🥇 16,348 (84 MiB / 39.4% CPU) |
9,898 (52 MiB / 18.6% CPU) |
15,804 (50 MiB / 31.4% CPU) |
6,117 (72 MiB / 41.8% CPU) |
13,286 (104 MiB / 41.2% CPU) |
| HTTP/1 · plain | HTTP/1 · TLS | 🥇 12,783 (94 MiB / 43.1% CPU) |
5,967 (74 MiB / 18.6% CPU) |
12,525 (55 MiB / 33.2% CPU) |
5,670 (73 MiB / 48% CPU) |
9,909 (124 MiB / 40.9% CPU) |
| HTTP/1 · plain | HTTP/2 · plain | 🥇 20,904 (88 MiB / 43.3% CPU) |
Not possible (no H2 upstream) |
8,778 (51 MiB / 30.6% CPU) |
6,122 (71 MiB / 44.3% CPU) |
15,420 (112 MiB / 40.6% CPU) |
| HTTP/1 · plain | HTTP/2 · TLS |
13,267 (101 MiB / 37.9% CPU) |
Not possible (no H2 upstream) |
12,220 (52 MiB / 31.2% CPU) |
7,310 (73 MiB / 48.7% CPU) |
🥇 18,474 (116 MiB / 41.3% CPU) |
| HTTP/1 · plain | HTTP/3 · QUIC |
4,693 (89 MiB / 48.9% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
3,091 (75 MiB / 23.7% CPU) |
🥇 5,445 (115 MiB / 38.5% CPU) |
| HTTP/1 · TLS | HTTP/1 · plain |
7,955 (94 MiB / 34.9% CPU) |
6,371 (74 MiB / 25.2% CPU) |
7,738 (65 MiB / 31.1% CPU) |
6,444 (80 MiB / 41.2% CPU) |
🥇 8,665 (132 MiB / 38.8% CPU) |
| HTTP/1 · TLS | HTTP/1 · TLS |
8,977 (98 MiB / 36.1% CPU) |
5,494 (83 MiB / 18.9% CPU) |
🥇 9,125 (68 MiB / 35.1% CPU) |
3,695 (81 MiB / 37.8% CPU) |
7,820 (139 MiB / 37.4% CPU) |
| HTTP/1 · TLS | HTTP/2 · plain |
9,860 (97 MiB / 39.1% CPU) |
Not possible (no H2 upstream) |
7,800 (66 MiB / 32.1% CPU) |
5,521 (79 MiB / 45.2% CPU) |
🥇 10,651 (118 MiB / 34.5% CPU) |
| HTTP/1 · TLS | HTTP/2 · TLS |
7,860 (163 MiB / 37% CPU) |
Not possible (no H2 upstream) |
7,448 (66 MiB / 35.7% CPU) |
4,624 (79 MiB / 40.9% CPU) |
🥇 11,054 (127 MiB / 34.9% CPU) |
| HTTP/1 · TLS | HTTP/3 · QUIC |
2,705 (109 MiB / 46.2% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
3,636 (81 MiB / 32.3% CPU) |
🥇 4,619 (123 MiB / 35.2% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 16,372 (91 MiB / 42.4% CPU) |
15,429 (58 MiB / 18.7% CPU) |
10,220 (56 MiB / 20.3% CPU) |
3,692 (75 MiB / 22.4% CPU) |
14,817 (105 MiB / 44.2% CPU) |
| HTTP/2 · plain | HTTP/1 · TLS | 🥇 12,382 (94 MiB / 46.4% CPU) |
7,196 (85 MiB / 18.3% CPU) |
5,558 (59 MiB / 20.8% CPU) |
3,155 (77 MiB / 21.5% CPU) |
12,044 (125 MiB / 45.8% CPU) |
| HTTP/2 · plain | HTTP/2 · plain | 🥇 36,700 (73 MiB / 30.8% CPU) |
Not possible (no H2 upstream) |
9,528 (57 MiB / 19.9% CPU) |
6,036 (72 MiB / 22% CPU) |
24,939 (111 MiB / 40.4% CPU) |
| HTTP/2 · plain | HTTP/2 · TLS | 🥇 30,656 (77 MiB / 30.9% CPU) |
Not possible (no H2 upstream) |
8,666 (58 MiB / 20.9% CPU) |
5,469 (73 MiB / 21.3% CPU) |
26,491 (114 MiB / 38.9% CPU) |
| HTTP/2 · plain | HTTP/3 · QUIC |
5,092 (95 MiB / 56.1% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
1,973 (75 MiB / 22.8% CPU) |
🥇 5,453 (118 MiB / 39.1% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 15,856 (92 MiB / 44.7% CPU) |
9,770 (73 MiB / 18.1% CPU) |
8,427 (67 MiB / 21.2% CPU) |
3,491 (81 MiB / 21.4% CPU) |
12,707 (115 MiB / 43.2% CPU) |
| HTTP/2 · TLS | HTTP/1 · TLS | 🥇 13,319 (96 MiB / 44.8% CPU) |
6,541 (91 MiB / 19% CPU) |
8,793 (69 MiB / 21.4% CPU) |
3,229 (83 MiB / 21.7% CPU) |
11,692 (123 MiB / 44.6% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 27,511 (84 MiB / 31.1% CPU) |
Not possible (no H2 upstream) |
8,259 (68 MiB / 20.9% CPU) |
5,966 (79 MiB / 21.7% CPU) |
19,566 (112 MiB / 42.1% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 30,544 (84 MiB / 31.1% CPU) |
Not possible (no H2 upstream) |
7,040 (68 MiB / 21.3% CPU) |
5,660 (79 MiB / 21.4% CPU) |
19,845 (116 MiB / 40.9% CPU) |
| HTTP/2 · TLS | HTTP/3 · QUIC |
5,713 (106 MiB / 45.9% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
2,513 (80 MiB / 22.9% CPU) |
🥇 7,097 (121 MiB / 40.4% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
4,598 (100 MiB / 46.2% CPU) |
0 (peak 6,020 · 62 MiB / 13.4% CPU) |
🥇 9,237 (67 MiB / 23.9% CPU) |
1,238 (86 MiB / 23.4% CPU) |
4,568 (183 MiB / 39% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
4,263 (117 MiB / 43.4% CPU) |
0 (peak 7,101 · 67 MiB / 16.4% CPU) |
🥇 8,098 (69 MiB / 27.4% CPU) |
1,138 (87 MiB / 27.8% CPU) |
5,599 (204 MiB / 41.9% CPU) |
| HTTP/3 · QUIC | HTTP/2 · plain |
6,267 (97 MiB / 45% CPU) |
Not possible (no H2 upstream) | 🥇 8,644 (67 MiB / 21.8% CPU) |
2,182 (83 MiB / 31.8% CPU) |
6,180 (179 MiB / 38.4% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS |
5,881 (107 MiB / 46% CPU) |
Not possible (no H2 upstream) | 🥇 8,089 (69 MiB / 24.6% CPU) |
2,252 (84 MiB / 30.9% CPU) |
7,557 (171 MiB / 39.6% CPU) |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
3,988 (97 MiB / 39.4% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
904 (81 MiB / 27.3% CPU) |
🥇 4,700 (182 MiB / 40.8% CPU) |
Same Client×Origin wires with interception on (compare-product 35092827644). 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 / comparison-group shard). 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. @ 8bfa7852: 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 |
13,180 (84 MiB / 39.4% CPU) |
13,546 (85 MiB / 38.2% CPU) |
0.81× | 0.83× |
| HTTP/1 · plain | HTTP/1 · TLS |
11,128 (95 MiB / 40% CPU) |
10,009 (94 MiB / 37.9% CPU) |
0.87× | 0.78× |
| HTTP/1 · plain | HTTP/2 · plain |
18,046 (91 MiB / 44.7% CPU) |
12,264 (93 MiB / 43.8% CPU) |
0.86× | 0.59× |
| HTTP/1 · plain | HTTP/2 · TLS |
12,795 (106 MiB / 40.2% CPU) |
17,562 (106 MiB / 42.6% CPU) |
0.96× | 1.32× |
| HTTP/1 · plain | HTTP/3 · QUIC |
4,402 (91 MiB / 47.8% CPU) |
4,047 (93 MiB / 45.6% CPU) |
0.94× | 0.86× |
| HTTP/1 · TLS | HTTP/1 · plain |
11,591 (95 MiB / 39.4% CPU) |
7,671 (96 MiB / 38.9% CPU) |
1.46× | 0.96× |
| HTTP/1 · TLS | HTTP/1 · TLS |
8,412 (99 MiB / 37.9% CPU) |
10,725 (99 MiB / 38.9% CPU) |
0.94× | 1.19× |
| HTTP/1 · TLS | HTTP/2 · plain |
10,748 (100 MiB / 41.7% CPU) |
9,978 (105 MiB / 39.5% CPU) |
1.09× | 1.01× |
| HTTP/1 · TLS | HTTP/2 · TLS |
11,660 (161 MiB / 39.5% CPU) |
6,751 (156 MiB / 37.8% CPU) |
1.48× | 0.86× |
| HTTP/1 · TLS | HTTP/3 · QUIC |
2,975 (98 MiB / 54.7% CPU) |
3,009 (103 MiB / 49.8% CPU) |
1.1× | 1.11× |
| HTTP/2 · plain | HTTP/1 · plain |
16,657 (90 MiB / 43.4% CPU) |
16,350 (90 MiB / 44.4% CPU) |
1.02× | 1× |
| HTTP/2 · plain | HTTP/1 · TLS |
14,896 (98 MiB / 46.9% CPU) |
11,821 (97 MiB / 47% CPU) |
1.2× | 0.95× |
| HTTP/2 · plain | HTTP/2 · plain |
28,786 (79 MiB / 34.8% CPU) |
29,833 (76 MiB / 36.4% CPU) |
0.78× | 0.81× |
| HTTP/2 · plain | HTTP/2 · TLS |
26,546 (80 MiB / 38.2% CPU) |
34,124 (83 MiB / 36.4% CPU) |
0.87× | 1.11× |
| HTTP/2 · plain | HTTP/3 · QUIC |
4,078 (95 MiB / 52.4% CPU) |
4,254 (94 MiB / 53.3% CPU) |
0.8× | 0.84× |
| HTTP/2 · TLS | HTTP/1 · plain |
15,130 (95 MiB / 48.3% CPU) |
15,199 (92 MiB / 45.1% CPU) |
0.95× | 0.96× |
| HTTP/2 · TLS | HTTP/1 · TLS |
15,237 (100 MiB / 41.4% CPU) |
13,176 (97 MiB / 47% CPU) |
1.14× | 0.99× |
| HTTP/2 · TLS | HTTP/2 · plain |
26,045 (87 MiB / 38% CPU) |
31,311 (86 MiB / 36.2% CPU) |
0.95× | 1.14× |
| HTTP/2 · TLS | HTTP/2 · TLS |
29,113 (89 MiB / 36.4% CPU) |
28,038 (90 MiB / 38.6% CPU) |
0.95× | 0.92× |
| HTTP/2 · TLS | HTTP/3 · QUIC |
5,228 (107 MiB / 46.1% CPU) |
3,875 (107 MiB / 45.1% CPU) |
0.92× | 0.68× |
| HTTP/3 · QUIC | HTTP/1 · plain |
4,428 (101 MiB / 45.8% CPU) |
5,675 (102 MiB / 47.5% CPU) |
0.96× | 1.23× |
| HTTP/3 · QUIC | HTTP/1 · TLS |
4,527 (116 MiB / 46.2% CPU) |
4,613 (116 MiB / 44.1% CPU) |
1.06× | 1.08× |
| HTTP/3 · QUIC | HTTP/2 · plain |
5,291 (99 MiB / 49.4% CPU) |
4,946 (99 MiB / 48.2% CPU) |
0.84× | 0.79× |
| HTTP/3 · QUIC | HTTP/2 · TLS |
4,271 (105 MiB / 46.1% CPU) |
4,538 (107 MiB / 48.8% CPU) |
0.73× | 0.77× |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
3,764 (95 MiB / 57.2% CPU) |
4,233 (93 MiB / 41.9% CPU) |
0.94× | 1.06× |
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). Pre-origin Plus middleware (CIDR/WAF/JWT/rate-limit) runs on H1 terminate-lite without SessionEventArgs; JWT caches successful bearer validations. Maintainer gate thresholds live under Maintainer notes.
Median of 3 repeats @ 6d2a7c9d. Source: Actions 33259699099. Warmup 2s / measure 8s; concurrency 8–64; sustain = median peak RPS among SLO-pass steps @ c=64. RPS cells show sustain; <sub> holds peak (when higher) plus (MiB / CPU%).
| Arm | Win | Linux | Win÷ | Lin÷ | Gate |
|---|---|---|---|---|---|
twp-cli-reverse-http1 vs library |
32,214 (peak 32,219 · 118 MiB / 43.4% CPU) |
47,734 (peak 47,844 · 151 MiB / 49.0% CPU) |
1.02× | 1.04× | ≥ 0.80× |
twp-cli-reverse-http1-tls vs library TLS |
26,495 (peak 26,651 · 138 MiB / 48.3% CPU) |
36,932 (peak 37,200 · 174 MiB / 48.5% CPU) |
1.01× | 1.00× | ≥ 0.80× |
twp-cli-reverse-http1-route vs CLI |
32,394 (peak 32,428 · 120 MiB / 47.7% CPU) |
47,369 (peak 47,398 · 149 MiB / 49.2% CPU) |
1.01× | 0.99× | ≥ 0.90× |
twp-cli-plus-base-http1 vs CLI |
32,475 (peak 32,695 · 123 MiB / 46.0% CPU) |
47,322 (peak 47,744 · 154 MiB / 49.8% CPU) |
1.01× | 0.99× | ≥ 0.90× |
twp-cli-plus-cache-http1 (cold) vs CLI |
34,029 (peak 34,460 · 118 MiB / 64.5% CPU) |
49,409 (peak 50,104 · 148 MiB / 62.3% CPU) |
1.06× | 1.04× | ≥ 0.70× |
twp-cli-intercept-http1 vs CLI |
25,219 (peak 25,377 · 124 MiB / 54.0% CPU) |
35,969 (peak 35,977 · 155 MiB / 51.2% CPU) |
0.78× | 0.75× | ≥ 0.70× |
twp-cli-plus-waf-http1 vs CLI |
31,653 (peak 31,837 · 124 MiB / 46.8% CPU) |
46,367 (peak 46,685 · 151 MiB / 49.6% CPU) |
0.98× | 0.97× | ≥ 0.80× |
twp-cli-plus-cidr-http1 vs CLI |
31,730 (peak 31,757 · 123 MiB / 51.1% CPU) |
46,828 (peak 47,093 · 150 MiB / 50.2% CPU) |
0.98× | 0.98× | ≥ 0.80× |
twp-cli-plus-jwt-http1 vs CLI |
30,326 (peak 30,386 · 138 MiB / 46.6% CPU) |
43,906 (peak 44,433 · 179 MiB / 49.5% CPU) |
0.94× | 0.92× | ≥ 0.70× |
twp-cli-plus-ratelimit-http1 vs CLI |
31,713 (peak 31,944 · 123 MiB / 48.8% CPU) |
46,435 (peak 46,860 · 151 MiB / 49.4% CPU) |
0.98× | 0.97× | ≥ 0.80× |
twp-cli-plus-resilience-http1 vs CLI |
32,373 (peak 32,416 · 128 MiB / 50.5% CPU) |
47,302 (peak 47,654 · 155 MiB / 49.5% CPU) |
1.00× | 0.99× | ≥ 0.85× |
twp-cli-plus-discovery-file-http1 vs CLI |
32,209 (peak 32,332 · 121 MiB / 46.5% CPU) |
47,485 (peak 47,770 · 151 MiB / 49.1% CPU) |
1.00× | 0.99× | ≥ 0.80× |
twp-cli-plus-metrics-scrape-http1 vs CLI |
32,192 (peak 32,427 · 126 MiB / 44.8% CPU) |
47,348 (peak 48,665 · 159 MiB / 49.5% CPU) |
1.00× | 0.99× | ≥ 0.80× |
twp-cli-plus-cache-hit-http1 vs cache cold |
34,015 (peak 34,022 · 117 MiB / 66.0% CPU) |
49,153 (peak 49,358 · 149 MiB / 63.1% CPU) |
1.00× | 0.99× | ≥ 0.90× |
twp-cli-static-http1 vs CLI |
53,078 (peak 53,980 · 109 MiB / 52.1% CPU) |
78,333 (peak 82,955 · 150 MiB / 48.4% CPU) |
1.65× | 1.64× | ≥ 0.85× |
twp-cli-logging-http1 vs CLI |
32,440 (peak 32,581 · 119 MiB / 47.4% CPU) |
47,624 (peak 48,604 · 150 MiB / 49.3% CPU) |
1.01× | 1.00× | ≥ 0.90× |
twp-cli-lb-leasttime-http1 vs route |
29,649 (peak 30,386 · 134 MiB / 54.7% CPU) |
44,790 (peak 44,825 · 180 MiB / 50.6% CPU) |
0.92× | 0.95× | ≥ 0.85× |
twp-cli-dialect-twp-http1 vs CLI |
32,461 (peak 32,780 · 114 MiB / 49.7% CPU) |
47,898 (peak 48,154 · 145 MiB / 49.3% CPU) |
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.
Same reverse matrix measured on Titanium 7.0 versus committed 6.0 baselines (33087088466). Median of 3 @ 0ef6d4dd. Source: Actions 33270571908. Sustain RPS @ c=64. Absolute 7.0÷6.0 ≈ 0.96–1.00× on same-protocol arms; peer-normalized ratios ≈ 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× |
Both OS CSVs passed the cross-version check for this run. MITM arms are measured with the product reverse matrix, not this reverse-only comparison.
Same runners and harness as the tiny-GET tables, but with larger bodies, POST, lossy links, TLS cost, and architecture-sensitive paths (slow consumer / early response / duplex). 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. Laptop numbers are on Performance Local Lab. How maintainers refresh these tables (modes, shards, paste scripts) is under Maintainer notes. Larger-body check: 64 / 256 KiB H2 TLS→H2 TLS is where body copy dominates headers — Titanium is behind YARP here (~0.69–0.76× at 64 KiB), unlike the tiny-GET H2↔H2 medals above.
Lossy link = userspace delay/drop shim (not kernel netem): TCP gets per-buffer delay + occasional whole-connection stalls (honest head-of-line for multiplexed HTTP/2); UDP gets per-datagram delay + drops (QUIC). Lossy tables publish HTTP/1, HTTP/2, and HTTP/3.
Median of 3 repeats on windows-latest @ 8bfa7852. Source: Actions 35092832912 (compare-bodies). Warmup 2s / measure 8s. RPS cells include (MiB / CPU%) footprints.
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
| Body | Client | Origin | TWP | nginx | YARP |
|---|---|---|---|---|---|
| 64 KiB | HTTP/1 · TLS | HTTP/1 · plain | 🥇 8,782 (114 MiB / 46.0% CPU) |
644 (142 MiB / 24.7% CPU) |
7,764 (135 MiB / 47.3% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/1 · plain | 🥇 8,184 (174 MiB / 46.0% CPU) |
564 (142 MiB / 24.8% CPU) |
7,013 (135 MiB / 50.5% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 3,876 (132 MiB / 38.6% CPU) |
Not possible (no QUIC) |
3,663 (188 MiB / 46.8% CPU) |
| 256 KiB | HTTP/1 · TLS | HTTP/1 · plain | 🥇 2,790 (129 MiB / 47.6% CPU) |
0 (peak 162 · 143 MiB / 24.8% CPU) |
2,586 (135 MiB / 48.0% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/1 · plain | 🥇 2,537 (153 MiB / 40.3% CPU) |
0 (peak 142 · 142 MiB / 24.7% CPU) |
1,909 (134 MiB / 45.1% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 1,121 (107 MiB / 39.8% CPU) |
Not possible (no QUIC) |
1,048 (170 MiB / 46.1% CPU) |
| 64 KiB | HTTP/2 · plain | HTTP/1 · plain | 🥇 11,532 (170 MiB / 42.4% CPU) |
2,087 (127 MiB / 24.8% CPU) |
9,578 (118 MiB / 45.0% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/2 · plain |
3,848 (77 MiB / 35.2% CPU) |
Not possible | 🥇 6,673 (140 MiB / 47.9% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/2 · TLS |
3,495 (78 MiB / 37.3% CPU) |
Not possible | 🥇 6,000 (140 MiB / 47.2% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/2 · TLS |
3,396 (125 MiB / 43.6% CPU) |
Not possible | 🥇 3,552 (184 MiB / 46.8% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 3,934 (138 MiB / 41.5% CPU) |
Not possible (no QUIC) |
3,307 (199 MiB / 49.0% CPU) |
| 256 KiB | HTTP/2 · plain | HTTP/1 · plain | 🥇 3,784 (144 MiB / 30.6% CPU) |
655 (127 MiB / 24.3% CPU) |
2,823 (123 MiB / 38.1% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/2 · plain |
1,382 (86 MiB / 33.1% CPU) |
Not possible | 🥇 1,808 (163 MiB / 47.4% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/2 · TLS |
1,277 (87 MiB / 34.7% CPU) |
Not possible | 🥇 1,410 (146 MiB / 44.2% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/2 · TLS |
869 (151 MiB / 42.2% CPU) |
Not possible | 🥇 979 (191 MiB / 44.0% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 1,090 (148 MiB / 40.6% CPU) |
Not possible (no QUIC) |
990 (198 MiB / 44.3% 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 @ 8bfa7852. Source: Actions 35092832912 (compare-bodies). Warmup 2s / measure 8s.
| Body | Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|---|
| 64 KiB | HTTP/1 · TLS | HTTP/1 · plain |
8,545 (175 MiB / 44.2% CPU) |
5,990 (100 MiB / 49.8% CPU) |
🥇 9,584 (83 MiB / 39.5% CPU) |
8,284 (132 MiB / 43.3% CPU) |
7,035 (164 MiB / 47.9% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/1 · plain | 🥇 5,771 (221 MiB / 40.2% CPU) |
1,831 (101 MiB / 16.9% CPU) |
4,786 (84 MiB / 24.4% CPU) |
4,353 (140 MiB / 23.6% CPU) |
4,800 (162 MiB / 48.5% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 5,873 (183 MiB / 43.5% CPU) |
0 (peak 1,851 · 129 MiB / 23.6% CPU) |
5,030 (88 MiB / 32.8% CPU) |
123 (139 MiB / 1.1% CPU) |
4,568 (231 MiB / 51.3% CPU) |
| 256 KiB | HTTP/1 · TLS | HTTP/1 · plain |
2,794 (129 MiB / 35.7% CPU) |
1,514 (100 MiB / 47.3% CPU) |
🥇 3,024 (83 MiB / 32.8% CPU) |
2,727 (145 MiB / 32.2% CPU) |
2,211 (170 MiB / 44.0% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/1 · plain |
1,563 (195 MiB / 37.2% CPU) |
595 (100 MiB / 21.3% CPU) |
1,588 (83 MiB / 21.1% CPU) |
🥇 1,900 (158 MiB / 20.8% CPU) |
1,355 (161 MiB / 45.0% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 1,519 (159 MiB / 42.6% CPU) |
0 (118 MiB / 20.6% CPU) |
1,344 (89 MiB / 30.3% CPU) |
25 (157 MiB / 0.7% CPU) |
1,304 (218 MiB / 47.4% CPU) |
| 64 KiB | HTTP/2 · plain | HTTP/1 · plain | 🥇 9,681 (237 MiB / 42.5% CPU) |
2,321 (80 MiB / 12.9% CPU) |
7,167 (70 MiB / 24.3% CPU) |
6,374 (132 MiB / 23.9% CPU) |
8,267 (153 MiB / 45.9% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/2 · plain |
3,709 (103 MiB / 38.7% CPU) |
Not possible |
2,594 (86 MiB / 24.7% CPU) |
🥇 5,209 (144 MiB / 22.1% CPU) |
4,981 (184 MiB / 46.5% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/2 · TLS |
2,716 (102 MiB / 38.4% CPU) |
Not possible |
1,584 (83 MiB / 24.5% CPU) |
3,262 (146 MiB / 23.0% CPU) |
🥇 3,359 (171 MiB / 45.6% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/2 · TLS |
3,060 (164 MiB / 53.0% CPU) |
Not possible |
2,380 (90 MiB / 35.2% CPU) |
1 (144 MiB / 0.1% CPU) |
🥇 3,219 (240 MiB / 49.1% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 3,926 (203 MiB / 44.0% CPU) |
0 (peak 1,472 · 143 MiB / 23.0% CPU) |
3,080 (94 MiB / 31.8% CPU) |
0 (140 MiB / 0.1% CPU) |
3,182 (240 MiB / 49.5% CPU) |
| 256 KiB | HTTP/2 · plain | HTTP/1 · plain |
2,768 (211 MiB / 36.3% CPU) |
877 (79 MiB / 20.1% CPU) |
2,746 (70 MiB / 22.6% CPU) |
🥇 3,006 (152 MiB / 22.6% CPU) |
2,859 (157 MiB / 38.6% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/2 · plain |
1,203 (113 MiB / 33.7% CPU) |
Not possible |
772 (90 MiB / 24.3% CPU) |
🥇 1,769 (169 MiB / 22.0% CPU) |
1,350 (187 MiB / 41.8% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/2 · TLS |
804 (109 MiB / 33.0% CPU) |
Not possible |
433 (82 MiB / 24.6% CPU) |
🥇 983 (162 MiB / 22.5% CPU) |
954 (179 MiB / 41.9% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/2 · TLS |
847 (193 MiB / 51.2% CPU) |
Not possible |
706 (93 MiB / 36.1% CPU) |
0 (165 MiB / 0.1% CPU) |
🥇 948 (230 MiB / 46.3% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 1,090 (208 MiB / 45.0% CPU) |
0 (peak 216 · 144 MiB / 21.9% CPU) |
872 (96 MiB / 32.0% CPU) |
0 (148 MiB / 0.2% CPU) |
989 (245 MiB / 47.6% 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 @ 8bfa7852. Source: Actions 35092838467 (compare-post).
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
| Client | Origin | TWP | nginx | YARP |
|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain | 🥇 6,265 (98 MiB / 43.7% CPU) |
361 (142 MiB / 24.7% CPU) |
4,403 (137 MiB / 54.9% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 4,552 (190 MiB / 48.0% CPU) |
364 (144 MiB / 24.9% CPU) |
2,959 (134 MiB / 38.4% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,026 (179 MiB / 39.5% CPU) |
Not possible (no QUIC) |
1,928 (218 MiB / 49.6% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 6,752 (189 MiB / 45.0% CPU) |
1,799 (130 MiB / 24.7% CPU) |
6,174 (123 MiB / 52.5% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain |
8 (88 MiB / 0.2% CPU) |
Not possible | 🥇 3,734 (145 MiB / 47.7% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS |
8 (89 MiB / 0.1% CPU) |
Not possible | 🥇 2,795 (145 MiB / 46.3% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS |
1,801 (180 MiB / 45.2% CPU) |
Not possible | 🥇 1,870 (209 MiB / 49.3% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 1,930 (180 MiB / 41.5% CPU) |
Not possible (no QUIC) |
1,830 (221 MiB / 48.5% CPU) |
TWP leads H1 POST (1.5× YARP), H2 POST (1.2× YARP), and H3 POST (~1.1× YARP).
Median of 3 repeats @ 8bfa7852. Source: Actions 35092838467 (compare-post).
| Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain |
4,706 (131 MiB / 45.2% CPU) |
3,494 (100 MiB / 48.9% CPU) |
4,766 (85 MiB / 41.1% CPU) |
🥇 4,816 (133 MiB / 42.2% CPU) |
3,114 (174 MiB / 55.3% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 3,051 (210 MiB / 47.8% CPU) |
1,296 (113 MiB / 21.6% CPU) |
1,773 (82 MiB / 24.1% CPU) |
0 (peak 2,797 · 142 MiB / 22.5% CPU) |
2,483 (170 MiB / 48.6% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,881 (226 MiB / 44.1% CPU) |
457 (109 MiB / 25.0% CPU) |
2,078 (91 MiB / 35.9% CPU) |
0 (125 MiB / 0.1% CPU) |
2,552 (259 MiB / 49.6% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 5,393 (226 MiB / 44.3% CPU) |
2,294 (97 MiB / 22.5% CPU) |
2,629 (69 MiB / 23.7% CPU) |
0 (peak 4,450 · 133 MiB / 23.6% CPU) |
4,330 (160 MiB / 48.2% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain |
2 (117 MiB / 0.1% CPU) |
Not possible |
1,363 (86 MiB / 24.4% CPU) |
🥇 2,826 (141 MiB / 23.5% CPU) |
2,367 (181 MiB / 47.3% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS |
8 (121 MiB / 0.3% CPU) |
Not possible |
1,048 (87 MiB / 24.5% CPU) |
🥇 2,023 (147 MiB / 23.1% CPU) |
1,880 (175 MiB / 46.6% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 1,834 (240 MiB / 49.0% CPU) |
Not possible |
1,118 (95 MiB / 31.6% CPU) |
0 (126 MiB / 0.1% CPU) |
1,761 (255 MiB / 47.1% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 2,088 (238 MiB / 44.9% CPU) |
367 (120 MiB / 24.9% CPU) |
1,492 (95 MiB / 32.4% CPU) |
0 (126 MiB / 0.1% CPU) |
1,944 (257 MiB / 47.9% 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 @ 8bfa7852 — 35092841035 (compare-lossy).
Not possible: HAProxy and Envoy columns are omitted (no official Windows port). Not possible: HAProxy and Envoy columns are omitted (no official Windows port). Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
| Client | Origin | TWP | nginx | YARP |
|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain |
663 (112 MiB / 4.2% CPU) |
653 (142 MiB / 16.7% CPU) |
🥇 679 (123 MiB / 3.9% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain |
0 (peak 89 · 121 MiB / 1.4% CPU) |
0 (peak 17 · 142 MiB / 0.5% CPU) |
0 (peak 16 · 100 MiB / 0.7% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
0 (67 MiB / 0.0% CPU) |
Not possible (no QUIC) |
0 (79 MiB / 0.0% CPU) |
| HTTP/2 · plain | HTTP/1 · plain |
0 (peak 88 · 131 MiB / 0.9% CPU) |
0 (peak 17 · 128 MiB / 0.1% CPU) |
0 (peak 16 · 91 MiB / 0.7% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain |
0 (peak 8 · 67 MiB / 0.5% CPU) |
Not possible |
0 (peak 16 · 101 MiB / 0.9% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS |
0 (peak 9 · 72 MiB / 0.3% CPU) |
Not possible |
0 (peak 15 · 100 MiB / 0.6% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS |
0 (71 MiB / 0.0% CPU) |
Not possible |
0 (80 MiB / 0.0% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
0 (69 MiB / 0.0% CPU) |
Not possible (no QUIC) |
0 (79 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 re-measure kept above).
Median of 3 repeats @ 8bfa7852. Source: 35092841035 (compare-lossy; lossy H3 uses quic-http3).
| Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain |
1,214 (148 MiB / 10.2% CPU) |
🥇 1,219 (102 MiB / 8.2% CPU) |
1,218 (83 MiB / 4.8% CPU) |
1,209 (128 MiB / 6.6% CPU) |
1,211 (146 MiB / 13.1% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 312 (186 MiB / 5.2% CPU) |
40 (101 MiB / 0.2% CPU) |
40 (84 MiB / 0.2% CPU) |
40 (137 MiB / 0.3% CPU) |
40 (129 MiB / 1.1% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
342 (158 MiB / 10.4% CPU) |
100 (112 MiB / 1.8% CPU) |
60 (86 MiB / 2.7% CPU) |
🥇 1,578 (140 MiB / 20.3% CPU) |
321 (178 MiB / 15.2% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 337 (177 MiB / 5.2% CPU) |
40 (78 MiB / 0.1% CPU) |
41 (67 MiB / 0.1% CPU) |
40 (127 MiB / 0.3% CPU) |
41 (123 MiB / 1.0% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain |
0 (peak 16 · 97 MiB / 0.5% CPU) |
Not possible | 🥇 42 (90 MiB / 0.3% CPU) |
40 (136 MiB / 0.2% CPU) |
41 (133 MiB / 1.0% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS |
0 (peak 13 · 98 MiB / 0.5% CPU) |
Not possible | 🥇 41 (84 MiB / 0.4% CPU) |
40 (138 MiB / 0.4% CPU) |
41 (131 MiB / 1.2% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS |
325 (143 MiB / 19.3% CPU) |
Not possible |
66 (91 MiB / 3.2% CPU) |
🥇 1,459 (145 MiB / 23.4% CPU) |
349 (190 MiB / 20.2% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
325 (165 MiB / 10.9% CPU) |
99 (118 MiB / 1.9% CPU) |
63 (90 MiB / 3.1% CPU) |
🥇 1,456 (141 MiB / 22.1% CPU) |
350 (185 MiB / 18.6% CPU) |
TWP H2 HOL ≫ YARP (~7.7×). H3 TWP÷YARP ≈ 1×.
These runs isolate slow app readers, origin-early response, HTTP/2 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 @ 8bfa7852 (35092846209). Slow consumer = 256 KiB GET, 16 KiB read + 8 ms sleep. Early response = 64 KiB POST, origin writes after 8 KiB. Duplex HTTP/2 = overlapping 64 KiB POST on H2 TLS↔H2 TLS. WebSocket row = H1 Upgrade echo round-trips/sec (not RFC 8441; see WebSocket RFC 8441 table below).
Lossy-link runs (slow network) are already published above; they are not a slow app reader.
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
| Scenario | Client | Origin | TWP | nginx | YARP |
|---|---|---|---|---|---|
| Slow consumer (256 KiB GET, throttled client read) | HTTP/1 · TLS | HTTP/1 · plain | 🥇 248 (100 MiB / 4.9% CPU) |
192 (143 MiB / 24.7% CPU) |
240 (111 MiB / 4.6% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/1 · plain |
248 (121 MiB / 4.6% CPU) |
180 (142 MiB / 24.7% CPU) |
🥇 253 (112 MiB / 6.7% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/3 · QUIC | HTTP/1 · plain |
273 (102 MiB / 15.2% CPU) |
Not possible (no QUIC) | 🥇 274 (162 MiB / 15.7% CPU) |
| Early response (origin writes after first request chunk) | HTTP/1 · TLS | HTTP/1 · plain | 🥇 11,064 (101 MiB / 41.4% CPU) |
679 (143 MiB / 24.9% CPU) |
7,558 (138 MiB / 53.7% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/1 · plain |
2,870 (181 MiB / 40.4% CPU) |
0 (peak 343 · 144 MiB / 24.9% CPU) |
🥇 3,336 (135 MiB / 51.9% CPU) |
| Early response (origin writes after first request chunk) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,141 (149 MiB / 41.8% CPU) |
Not possible (no QUIC) |
1,888 (199 MiB / 52.7% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · plain | HTTP/1 · plain |
248 (106 MiB / 3.9% CPU) |
248 (127 MiB / 8.7% CPU) |
🥇 256 (104 MiB / 4.3% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/2 · TLS |
0 (peak 8 · 72 MiB / 0.3% CPU) |
Not possible | 🥇 256 (141 MiB / 4.9% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/2 · TLS |
0 (97 MiB / 0.1% CPU) |
Not possible |
0 (123 MiB / 0.2% CPU) |
| Duplex (both directions live) | HTTP/2 · TLS | HTTP/2 · TLS |
0 (95 MiB / 0.1% CPU) |
Not possible |
0 (114 MiB / 0.2% CPU) |
| Duplex (WebSocket / H1 Upgrade) | HTTP/1 · TLS | HTTP/1 · plain | 🥇 66,034 (97 MiB / 41.3% CPU) |
36,248 (143 MiB / 24.6% CPU) |
61,591 (89 MiB / 44.1% CPU) |
| Scenario | Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|---|
| Slow consumer (256 KiB GET, throttled client read) | HTTP/1 · TLS | HTTP/1 · plain |
468 (125 MiB / 9.2% CPU) |
416 (101 MiB / 9.4% CPU) |
🥇 472 (84 MiB / 5.8% CPU) |
469 (139 MiB / 6.2% CPU) |
419 (145 MiB / 14.0% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/1 · plain |
473 (157 MiB / 12.2% CPU) |
427 (102 MiB / 9.4% CPU) |
474 (84 MiB / 5.4% CPU) |
🥇 480 (157 MiB / 5.0% CPU) |
474 (148 MiB / 16.2% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 480 (135 MiB / 31.0% CPU) |
0 (peak 347 · 124 MiB / 18.5% CPU) |
479 (90 MiB / 7.8% CPU) |
1 (156 MiB / 0.2% CPU) |
476 (196 MiB / 37.0% CPU) |
| Early response (origin writes after first request chunk) | HTTP/1 · TLS | HTTP/1 · plain |
6,746 (148 MiB / 44.2% CPU) |
5,410 (101 MiB / 48.0% CPU) |
🥇 7,255 (84 MiB / 39.9% CPU) |
0 (peak 673 · 130 MiB / 6.6% CPU) |
4,645 (176 MiB / 55.1% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/1 · plain |
2,465 (210 MiB / 48.0% CPU) |
0 (peak 858 · 112 MiB / 15.2% CPU) |
🥇 2,509 (85 MiB / 24.7% CPU) |
0 (peak 2,574 · 145 MiB / 23.1% CPU) |
2,226 (171 MiB / 48.6% CPU) |
| Early response (origin writes after first request chunk) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 4,647 (224 MiB / 46.4% CPU) |
0 (peak 706 · 122 MiB / 24.5% CPU) |
2,866 (93 MiB / 32.9% CPU) |
0 (126 MiB / 0.1% CPU) |
3,107 (258 MiB / 47.4% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · plain | HTTP/1 · plain |
475 (139 MiB / 14.4% CPU) |
463 (79 MiB / 11.5% CPU) |
🥇 476 (68 MiB / 6.2% CPU) |
472 (150 MiB / 4.5% CPU) |
474 (149 MiB / 14.7% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/2 · TLS |
9 (96 MiB / 1.8% CPU) |
Not possible |
447 (90 MiB / 12.9% CPU) |
470 (157 MiB / 9.3% CPU) |
🥇 470 (172 MiB / 21.4% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/2 · TLS |
0 (117 MiB / 0.1% CPU) |
Not possible | 🥇 8 (95 MiB / 0.2% CPU) |
0 (peak 2,844 · 152 MiB / 19.7% CPU) |
0 (141 MiB / 0.1% CPU) |
| Duplex (both directions live) | HTTP/2 · TLS | HTTP/2 · TLS |
0 (118 MiB / 0.1% CPU) |
Not possible |
0 (93 MiB / 0.1% CPU) |
0 (peak 2,526 · 156 MiB / 17.2% CPU) |
0 (146 MiB / 0.2% CPU) |
| Duplex (WebSocket / H1 Upgrade) | HTTP/1 · TLS | HTTP/1 · plain |
45,832 (128 MiB / 44.4% CPU) |
🥇 49,425 (102 MiB / 36.9% CPU) |
48,657 (82 MiB / 39.8% CPU) |
46,062 (127 MiB / 41.2% CPU) |
41,388 (126 MiB / 45.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 @ 8bfa7852. Source: Actions 35092843724. Absolute RPS on GHA swings hard; prefer TWP÷YARP.
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
| Workload | TWP | nginx | YARP |
|---|---|---|---|
| Keep-alive · tiny GET | 🥇 21,760 (85 MiB / 49.0% CPU) |
9,611 (142 MiB / 24.8% CPU) |
19,339 (101 MiB / 48.2% CPU) |
| New-connection · tiny GET | 🥇 747 (89 MiB / 9.4% CPU) |
0 (peak 247 · 140 MiB / 24.2% CPU) |
732 (113 MiB / 9.5% CPU) |
| Keep-alive · 256 KiB GET | 🥇 2,948 (133 MiB / 45.9% CPU) |
0 (peak 158 · 143 MiB / 24.5% CPU) |
2,718 (128 MiB / 45.9% CPU) |
Median of 3 repeats @ 8bfa7852. Source: Actions 35092843724.
| Workload | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|
| Keep-alive · tiny GET |
23,990 (112 MiB / 50.2% CPU) |
28,472 (100 MiB / 40.9% CPU) |
🥇 28,484 (83 MiB / 43.2% CPU) |
17,372 (127 MiB / 58.6% CPU) |
20,581 (136 MiB / 50.5% CPU) |
| New-connection · tiny GET |
976 (116 MiB / 47.4% CPU) |
1,017 (100 MiB / 44.5% CPU) |
956 (82 MiB / 44.1% CPU) |
🥇 1,072 (129 MiB / 41.4% CPU) |
967 (152 MiB / 46.4% CPU) |
| Keep-alive · 256 KiB GET |
2,708 (128 MiB / 36.8% CPU) |
1,740 (100 MiB / 53.7% CPU) |
🥇 2,966 (84 MiB / 33.2% CPU) |
2,734 (146 MiB / 34.2% CPU) |
2,145 (172 MiB / 45.3% 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.
Unary Echo RPC/s @ c=64 over H2 TLS→H2 TLS for Titanium, YARP, nginx (grpc_pass), HAProxy, and Envoy (OS-possible peers only). Not folded into the architecture-sensitive tables. Median of 3 repeats @ 8bfa7852 — 35092856793.
| OS | Titanium | YARP | nginx | HAProxy | Envoy |
|---|---|---|---|---|---|
| Windows |
111,791 (116 MiB / 22.8% CPU) |
63,757 (120 MiB / 41.9% CPU) |
0 (143 MiB / 24.9% CPU) |
Not possible | Not possible |
| Linux |
78,834 (121 MiB / 32.5% CPU) |
45,952 (149 MiB / 42.2% CPU) |
0 (131 MiB / 25.1% CPU) |
18,339 (83 MiB / 24.2% CPU) |
30,733 (127 MiB / 21.4% CPU) |
| macOS |
18,711 (97 MiB / 24.0% CPU) |
13,574 (137 MiB / 34.5% CPU) |
0 (61 MiB / 3.1% CPU) |
0 (63 MiB / 18.4% CPU) |
7,199 (80 MiB / 20.7% CPU) |
Unary Echo RPC/s @ c=64 over H2 TLS → h2c (edge TLS, cleartext H2 origin). Peers: Titanium, YARP, HAProxy, Envoy. Mode: compare-grpc (*-grpc-h2c). Median of 3 repeats @ 8bfa7852 — 35092859116.
Not possible: nginx column omitted (no H2 upstream).
| OS | Titanium | YARP | HAProxy | Envoy |
|---|---|---|---|---|
| Windows |
60,626 (93 MiB / 24.4% CPU) |
34,219 (117 MiB / 47.2% CPU) |
Not possible | Not possible |
| Linux |
43,638 (126 MiB / 28.5% CPU) |
24,134 (169 MiB / 43.3% CPU) |
7,783 (82 MiB / 24.4% CPU) |
12,047 (128 MiB / 22.2% CPU) |
| macOS |
24,524 (95 MiB / 24.8% CPU) |
16,722 (130 MiB / 35.8% CPU) |
0 (65 MiB / 17.4% CPU) |
0 (79 MiB / 20.6% CPU) |
WebSocket echo round-trips/sec over dual-TLS H1 (proxy_ssl style). Mode: compare-ws-h1tls (*-duplex-ws-h1tls).
| OS | Titanium | YARP | nginx | HAProxy | Envoy |
|---|---|---|---|---|---|
| Windows |
66,023 (102 MiB / 42.5% CPU) |
63,442 (90 MiB / 46.5% CPU) |
31,084 (143 MiB / 24.7% CPU) |
Not possible | Not possible |
| Linux |
38,814 (137 MiB / 44.3% CPU) |
34,209 (136 MiB / 45.8% CPU) |
41,105 (108 MiB / 38.2% CPU) |
42,059 (85 MiB / 39.8% CPU) |
40,348 (127 MiB / 40.1% CPU) |
| macOS |
10,520 (144 MiB / 33.3% CPU) |
8,988 (145 MiB / 33.6% CPU) |
10,184 (81 MiB / 26.4% CPU) |
10,985 (67 MiB / 32.0% CPU) |
15,732 (81 MiB / 30.8% CPU) |
WebSocket echo over RFC 8441 extended CONNECT (H2 TLS client → H1 plain origin). Mode: compare-ws-h2 (*-duplex-ws-h2).
Not possible: nginx column omitted (no RFC 8441 extended CONNECT reverse).
| OS | Titanium | YARP | HAProxy | Envoy |
|---|---|---|---|---|
| Windows |
23,244 (172 MiB / 41.7% CPU) |
0 (165 MiB / 19.8% CPU) |
Not possible | Not possible |
| Linux |
1,530 (143 MiB / 16.9% CPU) |
0 (172 MiB / 25.2% CPU) |
1,535 (82 MiB / 5.4% CPU) |
0 (123 MiB / 0.3% CPU) |
| macOS |
4,262 (219 MiB / 29.2% CPU) |
0 (86 MiB / 12.1% CPU) |
4,087 (66 MiB / 35.2% CPU) |
0 (73 MiB / 0.6% CPU) |
| 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;
};CI cadence, shards, paste scripts, and gate floors for people refreshing these tables. Consumers can skip this section.
| 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 |
| Pre-wiki smoke |
compare-product-smoke (Linux 2 shards, repeats=1) |
~30–60 min; required before full product |
| Release / wiki |
compare-product (3 comparison-group shards × Win/Linux/mac) |
~2–2½h wall (Free account queues beyond 20 jobs) |
| Unary gRPC | compare-grpc |
Win/Linux/mac; H2↔H2 + H2→h2c (arm_shard 1/2, 2/2) |
| WebSocket dual-TLS | compare-ws-h1tls |
Win/Linux/mac; H1 TLS→H1 TLS echo |
| WebSocket RFC 8441 | compare-ws-h2 |
Win/Linux/mac; H2 TLS→H1 plain (nginx N/A) |
| Heavier tables |
compare-bodies (2 shards) / post / lossy / arch (3 shards) / tls-cost
|
dispatch independently |
See PERF-GATES.md.
--arm-shard i/n (workflow input arm_shard) partitions wiki rows (Client×Origin + heavier/arch workload suffix), not individual proxy arms. Paste unions shard CSVs (paste-compare-product-wiki.ps1 -RunIds …; heavier RUNS lists). Table “Source: Actions” may list several run URLs for one table — do not mix SHAs. Free GitHub accounts allow 20 concurrent jobs (~34 for a full suite); extras queue, they do not fail. Dispatch product (and cross-version) first when wall clock matters.
One wiki row = one GHA job’s Client×Origin cell set: TWP + YARP + nginx + HAProxy + Envoy (when the OS can run them) + TWP Lite + Full stay on the same VM. Shards split rows, not individual proxies — so TWP÷YARP and Lite÷Reverse remain same-job ratios. Do not compare absolute RPS across shards (different VMs).
- Reverse product signal: TWP÷YARP ≥ 0.75 (nginx / HAProxy / Envoy are wiki and chart peers only — no CI gate). Edition CLI/Plus ratios floor at 0.50.
- MITM overhead: Lite÷Reverse ≥ 0.50 and Full÷Reverse ≥ 0.50 (median of 3 GHA runs @ c=64). Absolute RPS moves with runner heat; ratios are the claim.
- Editions: see PERF-GATES.md and
validate-edition-gates.ps1. - Cross-version (Gate 2) before a major tag: run
compare-cross-version(reverse matrix, routes unset) ondevelopvs committed 6.0 baselines (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-*.csvProduct refresh and heavier tables:
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-product
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-editions
pwsh tools/RpsLoadProbe/validate-edition-gates.ps1 -CsvPath tools/RpsLoadProbe/results/rps-ramp-*.csv
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-saturationHarness details: RpsLoadProbe. 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 (intentional RPS tradeoff).