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 Apple Silicon 3-core / 7 GB. We compare products within an OS, not OS against OS, so 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). 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 Win/Linux tiny GET). With larger bodies, see Heavier reverse (ratios @
41f4adee).
- 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 OS-possible peers on that row (highest sustain RPS; on a tie, lower memory then lower CPU%). On tiny Reverse that is Titanium / nginx / YARP (plus HAProxy / Envoy on Linux/macOS). The same rule applies to heavier reverse, architecture-sensitive, TLS-cost, gRPC, and WebSocket peer tables. Gold on tiny GET 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 (no peer medal). 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 public-repo GitHub-hosted runners: Windows / Linux at 4 vCPU / 16 GiB / 14 GB SSD, macOS at macos-15 Apple Silicon (M1) 3-core / 7 GB (pinned, not macos-latest). macOS is Apple Silicon because that is what Mac users run today; the runner sizes differ, which is fine because we compare products within an OS. 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, Apple Silicon arm64) |
| CPU | 3 logical processors (Apple M1) |
| RAM | 7 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-arm64 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 |
Use the pinned macos-15 label (not macos-latest) so the image does not change between runs.
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 @ 41f4adee — 36853300134. 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 |
50,668 (55 MiB / 41.5% CPU) |
100.0% |
| origin-direct-bombardier | bombardier |
39,281 (56 MiB / 22.5% CPU) |
77.5% |
| bare-reverse-http1 | dotnet-httpclient |
25,315 (60 MiB / 47.6% CPU) |
50.0% |
| nginx-reverse-http1 | dotnet-httpclient |
13,599 (125 MiB / 24.9% CPU) |
26.8% |
| yarp-reverse-http1 | dotnet-httpclient |
21,373 (88 MiB / 49.9% CPU) |
42.2% |
| twp-reverse-http1 | dotnet-httpclient | 🥇 25,546 (73 MiB / 48.4% CPU) |
50.4% |
Linux (ubuntu-latest)
| Arm | Generator | RPS | % of origin-HttpClient |
|---|---|---|---|
| origin-direct | dotnet-httpclient |
67,804 (80 MiB / 41.9% CPU) |
100.0% |
| origin-direct-bombardier | bombardier |
40,543 (79 MiB / 34.5% CPU) |
59.8% |
| bare-reverse-http1 | dotnet-httpclient |
31,377 (69 MiB / 44.2% CPU) |
46.3% |
| nginx-reverse-http1 | dotnet-httpclient | 🥇 38,409 (75 MiB / 41.2% CPU) |
56.6% |
| yarp-reverse-http1 | dotnet-httpclient |
27,147 (114 MiB / 50.6% CPU) |
40.0% |
| twp-reverse-http1 | dotnet-httpclient |
31,727 (85 MiB / 49.7% CPU) |
46.8% |
Reverse peers are about 51–48% of the origin-direct HttpClient peak on this runner class (Win TWP 51.2%, Lin TWP 47.9%). 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 |
7,915 (142 MiB / 24.7% CPU) |
0.27× | 1.00× |
| yarp-reverse-http2 | dotnet-httpclient |
28,934 (95 MiB / 53.9% CPU) |
1.00× | 3.66× |
| twp-reverse-http2-cleartext | dotnet-httpclient | 🥇 35,415 (95 MiB / 50.6% CPU) |
1.22× | 4.47× |
Linux (ubuntu-latest)
| Arm | Generator | RPS | ÷YARP | ÷nginx |
|---|---|---|---|---|
| nginx-reverse-http2 | dotnet-httpclient |
14,622 (100 MiB / 19.3% CPU) |
0.51× | 1.00× |
| yarp-reverse-http2 | dotnet-httpclient |
28,734 (122 MiB / 50.0% CPU) |
1.00× | 1.97× |
| twp-reverse-http2-cleartext | dotnet-httpclient | 🥇 33,677 (116 MiB / 51.1% CPU) |
1.17× | 2.30× |
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 | 🥇 15,752 (140 MiB / 51.4% CPU) |
1.00× | — |
| twp-reverse-http3-cleartext | dotnet-httpclient |
14,716 (102 MiB / 43.2% CPU) |
0.93× | — |
Linux (ubuntu-latest)
| Arm | Generator | RPS | ÷YARP | ÷nginx |
|---|---|---|---|---|
| nginx-reverse-http3-cleartext | dotnet-httpclient |
0 (peak 14,970 · 107 MiB / 22.5% CPU) |
0.83× | 1.00× |
| yarp-reverse-http3-cleartext | dotnet-httpclient |
17,957 (181 MiB / 50.3% CPU) |
1.00× | 1.20× |
| twp-reverse-http3-cleartext | dotnet-httpclient | 🥇 19,545 (144 MiB / 48.6% CPU) |
1.09× | 1.31× |
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 @ 41f4adee — compare-product 36853254836. 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 | 🥇 25,880 (73 MiB / 47.6% CPU) |
14,026 (125 MiB / 24.8% CPU) |
21,496 (89 MiB / 49.4% CPU) |
| HTTP/1 · plain | HTTP/1 · TLS | 🥇 21,172 (88 MiB / 52.1% CPU) |
8,314 (135 MiB / 24.9% CPU) |
19,155 (98 MiB / 50.8% CPU) |
| HTTP/1 · plain | HTTP/2 · plain | 🥇 48,182 (110 MiB / 47.8% CPU) |
Not possible (no H2 upstream) |
42,621 (93 MiB / 49.2% CPU) |
| HTTP/1 · plain | HTTP/2 · TLS | 🥇 32,909 (118 MiB / 45.1% CPU) |
Not possible (no H2 upstream) |
30,006 (96 MiB / 46.6% CPU) |
| HTTP/1 · plain | HTTP/3 · QUIC | 🥇 18,481 (106 MiB / 50.6% CPU) |
Not possible (no H3 upstream) |
17,986 (120 MiB / 51.9% CPU) |
| HTTP/1 · TLS | HTTP/1 · plain | 🥇 28,020 (87 MiB / 48.7% CPU) |
13,783 (142 MiB / 24.6% CPU) |
24,447 (101 MiB / 48.5% CPU) |
| HTTP/1 · TLS | HTTP/1 · TLS | 🥇 19,084 (88 MiB / 48.2% CPU) |
6,979 (143 MiB / 24.6% CPU) |
17,016 (103 MiB / 46.6% CPU) |
| HTTP/1 · TLS | HTTP/2 · plain | 🥇 28,276 (111 MiB / 45.6% CPU) |
Not possible (no H2 upstream) |
26,212 (112 MiB / 46.9% CPU) |
| HTTP/1 · TLS | HTTP/2 · TLS | 🥇 33,428 (115 MiB / 43.8% CPU) |
Not possible (no H2 upstream) |
31,451 (108 MiB / 48% CPU) |
| HTTP/1 · TLS | HTTP/3 · QUIC | 🥇 16,002 (109 MiB / 50.8% CPU) |
Not possible (no H3 upstream) |
15,531 (126 MiB / 50.4% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 36,594 (86 MiB / 51.9% CPU) |
9,135 (127 MiB / 24.7% CPU) |
32,083 (86 MiB / 53.7% CPU) |
| HTTP/2 · plain | HTTP/1 · TLS | 🥇 38,670 (99 MiB / 49.9% CPU) |
9,822 (137 MiB / 24.8% CPU) |
33,958 (92 MiB / 49.4% CPU) |
| HTTP/2 · plain | HTTP/2 · plain | 🥇 109,500 (59 MiB / 29.9% CPU) |
Not possible (no H2 upstream) |
65,061 (94 MiB / 48.2% CPU) |
| HTTP/2 · plain | HTTP/2 · TLS | 🥇 86,785 (68 MiB / 28.5% CPU) |
Not possible (no H2 upstream) |
51,561 (101 MiB / 44.1% CPU) |
| HTTP/2 · plain | HTTP/3 · QUIC | 🥇 34,582 (127 MiB / 53.5% CPU) |
Not possible (no H3 upstream) |
32,128 (127 MiB / 50.6% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 35,805 (96 MiB / 51.7% CPU) |
8,246 (141 MiB / 24.6% CPU) |
29,300 (95 MiB / 53.7% CPU) |
| HTTP/2 · TLS | HTTP/1 · TLS | 🥇 30,622 (101 MiB / 52.4% CPU) |
6,318 (145 MiB / 24.7% CPU) |
25,261 (98 MiB / 52.6% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 95,497 (76 MiB / 28.9% CPU) |
Not possible (no H2 upstream) |
58,094 (103 MiB / 50.6% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 83,912 (74 MiB / 29.6% CPU) |
Not possible (no H2 upstream) |
49,700 (98 MiB / 49% CPU) |
| HTTP/2 · TLS | HTTP/3 · QUIC | 🥇 30,854 (135 MiB / 53.3% CPU) |
Not possible (no H3 upstream) |
26,509 (123 MiB / 53.3% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 20,480 (108 MiB / 43% CPU) |
Not possible (no QUIC) |
18,402 (146 MiB / 50.5% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 13,870 (110 MiB / 45.2% CPU) |
Not possible (no QUIC) |
13,299 (146 MiB / 49.3% CPU) |
| HTTP/3 · QUIC | HTTP/2 · plain | 🥇 34,720 (126 MiB / 47.6% CPU) |
Not possible (no H2 upstream) |
23,072 (151 MiB / 49.6% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 34,810 (137 MiB / 47% CPU) |
Not possible (no H2 upstream) |
21,949 (150 MiB / 47% CPU) |
| HTTP/3 · QUIC | HTTP/3 · QUIC | 🥇 16,836 (121 MiB / 40.3% CPU) |
Not possible (no H3 upstream) |
12,462 (156 MiB / 47.8% CPU) |
Same Client×Origin wires with interception on (compare-product 36853254836). 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.40× and Full ≥ 0.40× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.60× (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. @ 41f4adee: 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 |
25,573 (77 MiB / 48.5% CPU) |
25,028 (78 MiB / 48.6% CPU) |
0.99× | 0.97× |
| HTTP/1 · plain | HTTP/1 · TLS |
21,034 (90 MiB / 51.8% CPU) |
20,798 (93 MiB / 51.2% CPU) |
0.99× | 0.98× |
| HTTP/1 · plain | HTTP/2 · plain |
40,940 (102 MiB / 47.9% CPU) |
46,410 (114 MiB / 50.1% CPU) |
0.85× | 0.96× |
| HTTP/1 · plain | HTTP/2 · TLS |
32,079 (119 MiB / 47.2% CPU) |
31,772 (121 MiB / 44.2% CPU) |
0.97× | 0.97× |
| HTTP/1 · plain | HTTP/3 · QUIC |
18,244 (107 MiB / 52.9% CPU) |
17,950 (107 MiB / 51.5% CPU) |
0.99× | 0.97× |
| HTTP/1 · TLS | HTTP/1 · plain |
28,020 (93 MiB / 50.2% CPU) |
27,368 (88 MiB / 46.1% CPU) |
1× | 0.98× |
| HTTP/1 · TLS | HTTP/1 · TLS |
19,116 (91 MiB / 50.8% CPU) |
18,790 (92 MiB / 46.7% CPU) |
1× | 0.98× |
| HTTP/1 · TLS | HTTP/2 · plain |
28,104 (117 MiB / 45.4% CPU) |
27,256 (113 MiB / 46.4% CPU) |
0.99× | 0.96× |
| HTTP/1 · TLS | HTTP/2 · TLS |
33,145 (121 MiB / 46.7% CPU) |
32,502 (118 MiB / 46.2% CPU) |
0.99× | 0.97× |
| HTTP/1 · TLS | HTTP/3 · QUIC |
15,961 (111 MiB / 49.4% CPU) |
15,795 (116 MiB / 49.6% CPU) |
1× | 0.99× |
| HTTP/2 · plain | HTTP/1 · plain |
35,159 (96 MiB / 55.4% CPU) |
34,094 (96 MiB / 54.9% CPU) |
0.96× | 0.93× |
| HTTP/2 · plain | HTTP/1 · TLS |
36,635 (105 MiB / 51.8% CPU) |
36,515 (101 MiB / 49.4% CPU) |
0.95× | 0.94× |
| HTTP/2 · plain | HTTP/2 · plain |
84,445 (71 MiB / 40.3% CPU) |
80,594 (67 MiB / 41.7% CPU) |
0.77× | 0.74× |
| HTTP/2 · plain | HTTP/2 · TLS |
71,243 (70 MiB / 37.3% CPU) |
67,741 (75 MiB / 40% CPU) |
0.82× | 0.78× |
| HTTP/2 · plain | HTTP/3 · QUIC |
34,577 (129 MiB / 53.4% CPU) |
33,498 (136 MiB / 55.7% CPU) |
1× | 0.97× |
| HTTP/2 · TLS | HTTP/1 · plain |
33,615 (98 MiB / 53.5% CPU) |
33,523 (98 MiB / 54.9% CPU) |
0.94× | 0.94× |
| HTTP/2 · TLS | HTTP/1 · TLS |
28,964 (102 MiB / 53.2% CPU) |
28,825 (101 MiB / 55.5% CPU) |
0.95× | 0.94× |
| HTTP/2 · TLS | HTTP/2 · plain |
85,236 (82 MiB / 39% CPU) |
82,696 (87 MiB / 40.9% CPU) |
0.89× | 0.87× |
| HTTP/2 · TLS | HTTP/2 · TLS |
68,466 (77 MiB / 38.5% CPU) |
65,341 (80 MiB / 40.8% CPU) |
0.82× | 0.78× |
| HTTP/2 · TLS | HTTP/3 · QUIC |
30,486 (124 MiB / 52.6% CPU) |
29,988 (137 MiB / 54.1% CPU) |
0.99× | 0.97× |
| HTTP/3 · QUIC | HTTP/1 · plain |
20,113 (119 MiB / 45.2% CPU) |
19,554 (117 MiB / 44.1% CPU) |
0.98× | 0.95× |
| HTTP/3 · QUIC | HTTP/1 · TLS |
13,347 (120 MiB / 46.8% CPU) |
12,596 (123 MiB / 45.8% CPU) |
0.96× | 0.91× |
| HTTP/3 · QUIC | HTTP/2 · plain |
33,868 (129 MiB / 46.6% CPU) |
32,694 (131 MiB / 46.4% CPU) |
0.98× | 0.94× |
| HTTP/3 · QUIC | HTTP/2 · TLS |
33,817 (142 MiB / 47.2% CPU) |
32,979 (138 MiB / 46.2% CPU) |
0.97× | 0.95× |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
16,282 (116 MiB / 48.1% CPU) |
15,803 (118 MiB / 45.6% CPU) |
0.97× | 0.94× |
Median of 3 repeats on ubuntu-latest (4 vCPU / 16 GiB). Bare reverse 5×5 @ 41f4adee — compare-product 36853254836. 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 |
35,542 (85 MiB / 50% CPU) |
🥇 44,228 (76 MiB / 40.2% CPU) |
41,021 (66 MiB / 41.7% CPU) |
25,019 (115 MiB / 58.7% CPU) |
32,144 (117 MiB / 49.1% CPU) |
| HTTP/1 · plain | HTTP/1 · TLS |
53,775 (106 MiB / 48.5% CPU) |
🥇 63,560 (93 MiB / 41.2% CPU) |
60,644 (70 MiB / 43% CPU) |
47,353 (118 MiB / 52.7% CPU) |
50,568 (131 MiB / 47.6% CPU) |
| HTTP/1 · plain | HTTP/2 · plain | 🥇 42,126 (124 MiB / 50.1% CPU) |
Not possible (no H2 upstream) |
27,642 (66 MiB / 42.6% CPU) |
21,554 (116 MiB / 64.2% CPU) |
35,843 (123 MiB / 49.5% CPU) |
| HTTP/1 · plain | HTTP/2 · TLS | 🥇 38,179 (142 MiB / 49.2% CPU) |
Not possible (no H2 upstream) |
34,950 (66 MiB / 43.2% CPU) |
25,265 (117 MiB / 59.6% CPU) |
35,038 (131 MiB / 47.5% CPU) |
| HTTP/1 · plain | HTTP/3 · QUIC | 🥇 50,848 (164 MiB / 54.4% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
29,583 (121 MiB / 48.8% CPU) |
43,839 (170 MiB / 47% CPU) |
| HTTP/1 · TLS | HTTP/1 · plain |
24,280 (107 MiB / 49.9% CPU) |
🥇 28,463 (99 MiB / 41.4% CPU) |
27,842 (82 MiB / 43.4% CPU) |
17,181 (127 MiB / 58.9% CPU) |
20,677 (134 MiB / 51.2% CPU) |
| HTTP/1 · TLS | HTTP/1 · TLS |
24,232 (110 MiB / 48.3% CPU) |
27,604 (104 MiB / 41.2% CPU) |
🥇 27,745 (83 MiB / 42.8% CPU) |
19,127 (127 MiB / 54.2% CPU) |
21,175 (142 MiB / 49.3% CPU) |
| HTTP/1 · TLS | HTTP/2 · plain | 🥇 69,681 (151 MiB / 49.9% CPU) |
Not possible (no H2 upstream) |
47,137 (84 MiB / 42.8% CPU) |
54,016 (126 MiB / 52.9% CPU) |
62,753 (142 MiB / 48.7% CPU) |
| HTTP/1 · TLS | HTTP/2 · TLS | 🥇 25,328 (154 MiB / 47.6% CPU) |
Not possible (no H2 upstream) |
23,716 (83 MiB / 43.3% CPU) |
17,369 (126 MiB / 58.4% CPU) |
22,175 (140 MiB / 48.9% CPU) |
| HTTP/1 · TLS | HTTP/3 · QUIC | 🥇 21,737 (150 MiB / 53.2% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
11,174 (130 MiB / 55.1% CPU) |
19,611 (160 MiB / 49.6% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 77,078 (120 MiB / 52.3% CPU) |
29,472 (79 MiB / 18.5% CPU) |
46,455 (67 MiB / 24.4% CPU) |
33,118 (118 MiB / 22.9% CPU) |
70,854 (112 MiB / 47.4% CPU) |
| HTTP/2 · plain | HTTP/1 · TLS | 🥇 29,364 (125 MiB / 50.8% CPU) |
13,245 (101 MiB / 19.5% CPU) |
18,930 (71 MiB / 24.6% CPU) |
13,147 (118 MiB / 23.5% CPU) |
26,309 (128 MiB / 50.9% CPU) |
| HTTP/2 · plain | HTTP/2 · plain | 🥇 95,132 (90 MiB / 38% CPU) |
Not possible (no H2 upstream) |
29,728 (66 MiB / 24.3% CPU) |
24,027 (115 MiB / 21.6% CPU) |
54,791 (132 MiB / 47% CPU) |
| HTTP/2 · plain | HTTP/2 · TLS | 🥇 130,965 (88 MiB / 41.3% CPU) |
Not possible (no H2 upstream) |
42,285 (68 MiB / 24.5% CPU) |
41,750 (118 MiB / 20.9% CPU) |
88,980 (132 MiB / 45% CPU) |
| HTTP/2 · plain | HTTP/3 · QUIC | 🥇 28,674 (144 MiB / 50.3% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
4,339 (122 MiB / 25% CPU) |
26,529 (162 MiB / 46.4% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 39,560 (128 MiB / 51.7% CPU) |
17,620 (100 MiB / 19% CPU) |
24,964 (81 MiB / 24.3% CPU) |
17,152 (129 MiB / 22.7% CPU) |
33,979 (122 MiB / 48.6% CPU) |
| HTTP/2 · TLS | HTTP/1 · TLS | 🥇 59,236 (133 MiB / 49.1% CPU) |
27,000 (112 MiB / 19.1% CPU) |
38,045 (86 MiB / 24.6% CPU) |
30,525 (128 MiB / 22.6% CPU) |
54,530 (126 MiB / 46.9% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 73,087 (95 MiB / 35.9% CPU) |
Not possible (no H2 upstream) |
21,291 (82 MiB / 24.4% CPU) |
17,672 (125 MiB / 22.1% CPU) |
40,036 (127 MiB / 47.6% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 61,583 (100 MiB / 35.4% CPU) |
Not possible (no H2 upstream) |
23,639 (83 MiB / 24.2% CPU) |
19,934 (127 MiB / 20.6% CPU) |
38,518 (127 MiB / 45.6% CPU) |
| HTTP/2 · TLS | HTTP/3 · QUIC | 🥇 54,162 (190 MiB / 52.7% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
16,762 (129 MiB / 24.7% CPU) |
47,112 (176 MiB / 44.5% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
21,551 (145 MiB / 50% CPU) |
0 (peak 15,107 · 107 MiB / 22.6% CPU) |
🥇 24,083 (86 MiB / 26.1% CPU) |
3,989 (132 MiB / 24.5% CPU) |
18,996 (186 MiB / 50.3% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
19,461 (163 MiB / 48.5% CPU) |
0 (peak 14,824 · 117 MiB / 22.7% CPU) |
🥇 21,322 (90 MiB / 28.9% CPU) |
4,755 (136 MiB / 24.3% CPU) |
18,172 (199 MiB / 49.7% CPU) |
| HTTP/3 · QUIC | HTTP/2 · plain | 🥇 60,482 (201 MiB / 55.6% CPU) |
Not possible (no H2 upstream) |
54,740 (88 MiB / 25.2% CPU) |
20 (130 MiB / 0.1% CPU) |
51,309 (226 MiB / 47.2% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 25,276 (155 MiB / 50.1% CPU) |
Not possible (no H2 upstream) |
21,371 (86 MiB / 26.5% CPU) |
6 (129 MiB / 0.1% CPU) |
21,601 (193 MiB / 48% CPU) |
| HTTP/3 · QUIC | HTTP/3 · QUIC | 🥇 22,007 (157 MiB / 47.7% CPU) |
Not possible (no H3 upstream) | Not possible (no H3 upstream) |
2,856 (130 MiB / 24.9% CPU) |
17,736 (207 MiB / 46.7% CPU) |
Same Client×Origin wires with interception on (compare-product 36853254836). 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.40× and Full ≥ 0.40× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.60× (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. @ 41f4adee: 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 |
36,120 (91 MiB / 49.8% CPU) |
35,316 (89 MiB / 48.6% CPU) |
1.02× | 0.99× |
| HTTP/1 · plain | HTTP/1 · TLS |
54,813 (111 MiB / 48.3% CPU) |
53,246 (110 MiB / 48.4% CPU) |
1.02× | 0.99× |
| HTTP/1 · plain | HTTP/2 · plain |
40,743 (131 MiB / 53% CPU) |
39,266 (127 MiB / 53.2% CPU) |
0.97× | 0.93× |
| HTTP/1 · plain | HTTP/2 · TLS |
37,310 (144 MiB / 52.2% CPU) |
36,668 (152 MiB / 50.5% CPU) |
0.98× | 0.96× |
| HTTP/1 · plain | HTTP/3 · QUIC |
49,696 (166 MiB / 56% CPU) |
49,175 (164 MiB / 55.2% CPU) |
0.98× | 0.97× |
| HTTP/1 · TLS | HTTP/1 · plain |
23,938 (113 MiB / 49.9% CPU) |
23,457 (115 MiB / 50% CPU) |
0.99× | 0.97× |
| HTTP/1 · TLS | HTTP/1 · TLS |
24,270 (112 MiB / 48.4% CPU) |
24,135 (112 MiB / 48% CPU) |
1× | 1× |
| HTTP/1 · TLS | HTTP/2 · plain |
67,032 (166 MiB / 50.1% CPU) |
65,781 (158 MiB / 51% CPU) |
0.96× | 0.94× |
| HTTP/1 · TLS | HTTP/2 · TLS |
23,602 (163 MiB / 49.4% CPU) |
23,843 (170 MiB / 48.6% CPU) |
0.93× | 0.94× |
| HTTP/1 · TLS | HTTP/3 · QUIC |
21,552 (151 MiB / 53% CPU) |
20,790 (156 MiB / 53.9% CPU) |
0.99× | 0.96× |
| HTTP/2 · plain | HTTP/1 · plain |
76,412 (124 MiB / 52.9% CPU) |
73,763 (120 MiB / 53% CPU) |
0.99× | 0.96× |
| HTTP/2 · plain | HTTP/1 · TLS |
28,157 (124 MiB / 52% CPU) |
27,650 (129 MiB / 52.1% CPU) |
0.96× | 0.94× |
| HTTP/2 · plain | HTTP/2 · plain |
71,938 (94 MiB / 43.2% CPU) |
66,304 (95 MiB / 42.9% CPU) |
0.76× | 0.7× |
| HTTP/2 · plain | HTTP/2 · TLS |
111,003 (103 MiB / 44.1% CPU) |
108,771 (106 MiB / 41.6% CPU) |
0.85× | 0.83× |
| HTTP/2 · plain | HTTP/3 · QUIC |
28,202 (145 MiB / 50.3% CPU) |
27,788 (144 MiB / 50.5% CPU) |
0.98× | 0.97× |
| HTTP/2 · TLS | HTTP/1 · plain |
37,310 (124 MiB / 52.7% CPU) |
37,209 (127 MiB / 52.4% CPU) |
0.94× | 0.94× |
| HTTP/2 · TLS | HTTP/1 · TLS |
57,688 (133 MiB / 49.9% CPU) |
55,972 (134 MiB / 49.4% CPU) |
0.97× | 0.94× |
| HTTP/2 · TLS | HTTP/2 · plain |
56,665 (102 MiB / 39.9% CPU) |
53,179 (111 MiB / 40.9% CPU) |
0.78× | 0.73× |
| HTTP/2 · TLS | HTTP/2 · TLS |
51,615 (111 MiB / 40.1% CPU) |
48,871 (113 MiB / 39.6% CPU) |
0.84× | 0.79× |
| HTTP/2 · TLS | HTTP/3 · QUIC |
52,800 (185 MiB / 51.8% CPU) |
51,956 (189 MiB / 52.1% CPU) |
0.97× | 0.96× |
| HTTP/3 · QUIC | HTTP/1 · plain |
20,377 (143 MiB / 50.5% CPU) |
20,027 (140 MiB / 50.4% CPU) |
0.95× | 0.93× |
| HTTP/3 · QUIC | HTTP/1 · TLS |
18,610 (168 MiB / 48.9% CPU) |
18,240 (168 MiB / 48.8% CPU) |
0.96× | 0.94× |
| HTTP/3 · QUIC | HTTP/2 · plain |
58,870 (200 MiB / 57.1% CPU) |
59,642 (195 MiB / 56.5% CPU) |
0.97× | 0.99× |
| HTTP/3 · QUIC | HTTP/2 · TLS |
25,006 (155 MiB / 50.1% CPU) |
24,294 (158 MiB / 50.2% CPU) |
0.99× | 0.96× |
| HTTP/3 · QUIC | HTTP/3 · QUIC |
21,690 (163 MiB / 50.4% CPU) |
20,322 (161 MiB / 50.5% CPU) |
0.99× | 0.92× |
Pending re-measure on macos-15 (Apple Silicon M1, 3-core / 7 GB): cells below read Not measured until the Mac shards finish. Bare reverse 5×5 @ 41f4adee. 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-arm64 1.36.7), Homebrew libmsquic (+ DYLD_*), and YARP. Use the pinned macos-15 label, not macos-latest. 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 | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/1 · plain | HTTP/1 · TLS | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/1 · plain | HTTP/2 · plain | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/1 · plain | HTTP/2 · TLS | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/1 · plain | HTTP/3 · QUIC | Not measured | Not possible (no H3 upstream) | Not possible (no H3 upstream) | Not measured | Not measured |
| HTTP/1 · TLS | HTTP/1 · plain | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/1 · TLS | HTTP/1 · TLS | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/1 · TLS | HTTP/2 · plain | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/1 · TLS | HTTP/2 · TLS | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/1 · TLS | HTTP/3 · QUIC | Not measured | Not possible (no H3 upstream) | Not possible (no H3 upstream) | Not measured | Not measured |
| HTTP/2 · plain | HTTP/1 · plain | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/2 · plain | HTTP/1 · TLS | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/2 · plain | HTTP/2 · plain | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/2 · plain | HTTP/2 · TLS | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/2 · plain | HTTP/3 · QUIC | Not measured | Not possible (no H3 upstream) | Not possible (no H3 upstream) | Not measured | Not measured |
| HTTP/2 · TLS | HTTP/1 · plain | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/2 · TLS | HTTP/1 · TLS | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/2 · TLS | HTTP/2 · plain | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/2 · TLS | HTTP/2 · TLS | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/2 · TLS | HTTP/3 · QUIC | Not measured | Not possible (no H3 upstream) | Not possible (no H3 upstream) | Not measured | Not measured |
| HTTP/3 · QUIC | HTTP/1 · plain | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/3 · QUIC | HTTP/1 · TLS | Not measured | Not measured | Not measured | Not measured | Not measured |
| HTTP/3 · QUIC | HTTP/2 · plain | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/3 · QUIC | HTTP/2 · TLS | Not measured | Not possible (no H2 upstream) | Not measured | Not measured | Not measured |
| HTTP/3 · QUIC | HTTP/3 · QUIC | Not measured | Not possible (no H3 upstream) | Not possible (no H3 upstream) | Not measured | Not measured |
Same Client×Origin wires with interception on (compare-product 36853254836). 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.40× and Full ≥ 0.40× reverse sustain @ c=64 (median of 3 GHA runs); reverse TWP÷YARP ≥ 0.60× (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. @ 41f4adee: 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 |
|---|
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 @ 41f4adee. Source: Actions 36853305087. 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 |
66,323 (131 MiB / 45.5% CPU) |
34,106 (166 MiB / 50.4% CPU) |
1.03× | 1.08× | ≥ 0.80× |
twp-cli-reverse-http1-tls vs library TLS |
56,976 (145 MiB / 45.7% CPU) |
24,583 (197 MiB / 50.3% CPU) |
1.00× | 1.04× | ≥ 0.80× |
twp-cli-reverse-http1-route vs CLI |
66,646 (135 MiB / 47.6% CPU) |
33,126 (169 MiB / 50.1% CPU) |
1.00× | 0.97× | ≥ 0.90× |
twp-cli-plus-base-http1 vs CLI |
66,656 (137 MiB / 47.2% CPU) |
34,197 (170 MiB / 50.5% CPU) |
1.01× | 1.00× | ≥ 0.90× |
twp-cli-plus-cache-http1 (cold) vs CLI |
58,304 (140 MiB / 50.0% CPU) |
28,354 (175 MiB / 54.0% CPU) |
0.88× | 0.83× | ≥ 0.70× |
twp-cli-intercept-http1 vs CLI |
53,188 (138 MiB / 49.4% CPU) |
24,664 (176 MiB / 52.9% CPU) |
0.80× | 0.72× | ≥ 0.70× |
twp-cli-plus-waf-http1 vs CLI |
63,745 (134 MiB / 45.3% CPU) |
32,738 (170 MiB / 51.2% CPU) |
0.96× | 0.96× | ≥ 0.80× |
twp-cli-plus-cidr-http1 vs CLI |
64,199 (133 MiB / 47.6% CPU) |
32,903 (170 MiB / 51.0% CPU) |
0.97× | 0.96× | ≥ 0.80× |
twp-cli-plus-jwt-http1 vs CLI |
61,258 (149 MiB / 45.8% CPU) |
30,232 (190 MiB / 50.7% CPU) |
0.92× | 0.89× | ≥ 0.70× |
twp-cli-plus-ratelimit-http1 vs CLI |
63,961 (141 MiB / 47.0% CPU) |
32,384 (170 MiB / 51.2% CPU) |
0.96× | 0.95× | ≥ 0.80× |
twp-cli-plus-resilience-http1 vs CLI |
65,742 (144 MiB / 48.0% CPU) |
33,765 (174 MiB / 50.6% CPU) |
0.99× | 0.99× | ≥ 0.85× |
twp-cli-plus-discovery-file-http1 vs CLI |
65,441 (139 MiB / 47.0% CPU) |
34,153 (172 MiB / 50.6% CPU) |
0.99× | 1.00× | ≥ 0.80× |
twp-cli-plus-metrics-scrape-http1 vs CLI |
65,919 (140 MiB / 46.8% CPU) |
33,848 (175 MiB / 50.3% CPU) |
0.99× | 0.99× | ≥ 0.80× |
twp-cli-plus-cache-hit-http1 vs cache cold |
58,870 (147 MiB / 48.7% CPU) |
28,214 (174 MiB / 53.4% CPU) |
1.01× | 1.00× | ≥ 0.90× |
twp-cli-static-http1 vs CLI |
114,525 (130 MiB / 49.1% CPU) |
54,684 (162 MiB / 50.2% CPU) |
1.73× | 1.60× | ≥ 0.85× |
twp-cli-logging-http1 vs CLI |
67,217 (139 MiB / 45.5% CPU) |
33,844 (163 MiB / 50.8% CPU) |
1.01× | 0.99× | ≥ 0.90× |
twp-cli-lb-leasttime-http1 vs route |
63,630 (144 MiB / 50.9% CPU) |
31,242 (191 MiB / 52.4% CPU) |
0.95× | 0.94× | ≥ 0.85× |
twp-cli-dialect-twp-http1 vs CLI |
66,493 (134 MiB / 46.4% CPU) |
33,965 (158 MiB / 50.3% CPU) |
1.00× | 1.00× | ≥ 0.90× |
validate-edition-gates.ps1 passed on both OS for this run. Library baselines @ c=64 (same job): Win H1 64191 / TLS 56711; Linux H1 31717 / TLS 23716. 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 — ratios vs YARP are in the tables below (@ 41f4adee), 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 is loss% only (no per-datagram delay) + drops (QUIC / MsQuic-safe). Lossy tables publish HTTP/1, HTTP/2, and HTTP/3.
Median of 3 repeats on windows-latest @ 41f4adee. Source: Actions 36853268072 (compare-bodies). Warmup 2s / measure 8s. RPS cells include (MiB / CPU%) footprints.
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 | 🥇 9,134 (97 MiB / 44.1% CPU) |
620 (141 MiB / 24.7% CPU) |
6,985 (134 MiB / 41.5% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/1 · plain | 🥇 11,535 (174 MiB / 46.7% CPU) |
814 (141 MiB / 24.8% CPU) |
9,117 (134 MiB / 49.1% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 3,937 (138 MiB / 37.4% CPU) |
Not possible (no QUIC) |
3,576 (196 MiB / 48.7% CPU) |
| 256 KiB | HTTP/1 · TLS | HTTP/1 · plain | 🥇 2,569 (123 MiB / 43.3% CPU) |
167 (142 MiB / 24.8% CPU) |
2,154 (129 MiB / 45.6% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/1 · plain | 🥇 3,307 (152 MiB / 39.9% CPU) |
197 (142 MiB / 24.7% CPU) |
2,344 (134 MiB / 42.8% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · plain |
1,076 (109 MiB / 39.1% CPU) |
Not possible (no QUIC) | 🥇 1,086 (165 MiB / 45.0% CPU) |
| 64 KiB | HTTP/2 · plain | HTTP/1 · plain | 🥇 13,884 (175 MiB / 39.7% CPU) |
3,252 (127 MiB / 24.8% CPU) |
13,150 (111 MiB / 44.4% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/2 · plain | 🥇 9,634 (114 MiB / 38.6% CPU) |
Not possible |
6,556 (138 MiB / 49.3% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 10,247 (120 MiB / 38.7% CPU) |
Not possible |
7,632 (131 MiB / 48.6% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 3,899 (124 MiB / 39.4% CPU) |
Not possible |
3,497 (187 MiB / 46.7% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 5,698 (143 MiB / 42.4% CPU) |
Not possible (no QUIC) |
4,442 (202 MiB / 48.0% CPU) |
| 256 KiB | HTTP/2 · plain | HTTP/1 · plain | 🥇 4,390 (145 MiB / 30.5% CPU) |
978 (127 MiB / 24.5% CPU) |
3,435 (123 MiB / 35.5% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/2 · plain | 🥇 2,521 (142 MiB / 36.1% CPU) |
Not possible |
1,810 (167 MiB / 46.0% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 2,262 (139 MiB / 36.4% CPU) |
Not possible |
1,776 (148 MiB / 42.8% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/2 · TLS |
880 (149 MiB / 42.4% CPU) |
Not possible | 🥇 995 (198 MiB / 43.8% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 1,389 (151 MiB / 39.5% CPU) |
Not possible (no QUIC) |
1,205 (203 MiB / 46.2% CPU) |
nginx/Windows collapses on large reverse bodies in this harness; treat as same-OS only. H1 TLS 64 KiB ≈ 1.13× YARP; 256 KiB ≈ 1.08×. H2→H1 64 KiB ≈ 1.17×; H3→H1 64 KiB ≈ 1.06×.
Median of 3 repeats @ 41f4adee. Source: Actions 36853268072 (compare-bodies). Warmup 2s / measure 8s.
| Body | Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|---|
| 64 KiB | HTTP/1 · TLS | HTTP/1 · plain |
8,426 (123 MiB / 44.4% CPU) |
5,076 (100 MiB / 49.4% CPU) |
🥇 8,950 (84 MiB / 40.0% CPU) |
7,261 (131 MiB / 46.1% CPU) |
6,410 (163 MiB / 48.9% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/1 · plain | 🥇 13,272 (242 MiB / 39.0% CPU) |
3,473 (102 MiB / 14.4% CPU) |
11,621 (86 MiB / 24.2% CPU) |
10,632 (141 MiB / 23.7% CPU) |
11,543 (155 MiB / 44.1% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 5,644 (184 MiB / 44.8% CPU) |
1,651 (peak 1,696 · 105 MiB / 22.4% CPU) |
4,139 (89 MiB / 27.6% CPU) |
0 (138 MiB / 0.2% CPU) |
4,274 (225 MiB / 51.4% CPU) |
| 256 KiB | HTTP/1 · TLS | HTTP/1 · plain |
2,724 (126 MiB / 36.8% CPU) |
1,731 (100 MiB / 53.5% CPU) |
🥇 2,959 (83 MiB / 33.5% CPU) |
2,697 (145 MiB / 33.9% CPU) |
2,139 (168 MiB / 45.5% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/1 · plain |
2,976 (223 MiB / 28.9% CPU) |
1,038 (102 MiB / 14.1% CPU) |
🥇 3,705 (83 MiB / 18.9% CPU) |
3,680 (162 MiB / 19.6% CPU) |
3,086 (159 MiB / 37.0% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 1,464 (157 MiB / 43.0% CPU) |
448 (109 MiB / 23.9% CPU) |
1,285 (89 MiB / 31.2% CPU) |
805 (154 MiB / 20.2% CPU) |
1,263 (217 MiB / 47.9% CPU) |
| 64 KiB | HTTP/2 · plain | HTTP/1 · plain | 🥇 19,850 (244 MiB / 41.2% CPU) |
4,660 (80 MiB / 14.4% CPU) |
14,246 (69 MiB / 24.2% CPU) |
16,092 (130 MiB / 23.6% CPU) |
16,776 (139 MiB / 40.5% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/2 · plain | 🥇 5,497 (158 MiB / 34.4% CPU) |
Not possible |
2,086 (89 MiB / 24.6% CPU) |
4,607 (144 MiB / 22.0% CPU) |
4,582 (182 MiB / 46.7% CPU) |
| 64 KiB | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 9,858 (184 MiB / 38.2% CPU) |
Not possible |
4,172 (82 MiB / 24.6% CPU) |
7,131 (146 MiB / 22.6% CPU) |
8,403 (186 MiB / 43.4% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 3,737 (171 MiB / 45.1% CPU) |
Not possible |
2,193 (92 MiB / 35.0% CPU) |
12 (146 MiB / 0.2% CPU) |
3,051 (237 MiB / 48.6% CPU) |
| 64 KiB | HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 8,245 (219 MiB / 41.4% CPU) |
2,792 (109 MiB / 23.1% CPU) |
7,599 (97 MiB / 34.2% CPU) |
0 (141 MiB / 0.1% CPU) |
7,119 (247 MiB / 48.1% CPU) |
| 256 KiB | HTTP/2 · plain | HTTP/1 · plain |
4,671 (221 MiB / 30.6% CPU) |
1,537 (80 MiB / 15.7% CPU) |
4,782 (70 MiB / 18.6% CPU) |
🥇 4,868 (157 MiB / 20.2% CPU) |
4,754 (156 MiB / 31.8% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/2 · plain | 🥇 1,766 (176 MiB / 32.2% CPU) |
Not possible |
590 (91 MiB / 24.4% CPU) |
1,578 (171 MiB / 21.5% CPU) |
1,296 (191 MiB / 42.6% CPU) |
| 256 KiB | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 2,642 (169 MiB / 32.6% CPU) |
Not possible |
1,184 (88 MiB / 24.3% CPU) |
1,995 (162 MiB / 22.5% CPU) |
2,328 (185 MiB / 38.8% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/2 · TLS |
832 (188 MiB / 51.0% CPU) |
Not possible |
629 (94 MiB / 36.7% CPU) |
651 (159 MiB / 22.3% CPU) |
🥇 906 (225 MiB / 46.3% CPU) |
| 256 KiB | HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 2,299 (192 MiB / 40.3% CPU) |
719 (123 MiB / 23.7% CPU) |
1,886 (96 MiB / 29.9% CPU) |
1,235 (143 MiB / 20.2% CPU) |
1,924 (238 MiB / 44.8% CPU) |
On this GHA pass TWP÷YARP H1 TLS ≈ 1.21× (64 KiB) / 1.26× (256 KiB); H2→H1 ≈ 1.20× / 1.15×; H3→H1 ≈ 1.29× / 1.16×. TWP÷nginx H1 TLS ≈ 1.43 / 1.85. Absolute RPS swings by VM; prefer ratios.
Median of 3 repeats on windows-latest @ 41f4adee. Source: Actions 36853276459 (compare-post).
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
| Client | Origin | TWP | nginx | YARP |
|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain | 🥇 6,621 (96 MiB / 44.6% CPU) |
382 (144 MiB / 24.7% CPU) |
4,412 (137 MiB / 56.9% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 4,572 (178 MiB / 48.4% CPU) |
375 (144 MiB / 24.8% CPU) |
3,938 (135 MiB / 51.0% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,239 (175 MiB / 40.5% CPU) |
Not possible (no QUIC) |
2,223 (218 MiB / 49.3% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 6,806 (178 MiB / 46.5% CPU) |
1,946 (130 MiB / 24.8% CPU) |
6,321 (124 MiB / 51.1% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 5,685 (123 MiB / 38.1% CPU) |
Not possible |
3,894 (143 MiB / 48.9% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 4,157 (123 MiB / 38.9% CPU) |
Not possible |
3,060 (142 MiB / 47.3% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 2,301 (164 MiB / 43.3% CPU) |
Not possible |
2,018 (206 MiB / 48.5% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 2,104 (183 MiB / 41.8% CPU) |
Not possible (no QUIC) |
2,002 (215 MiB / 49.3% CPU) |
TWP leads H1 POST (1.5x YARP on Windows and Linux), H2→H1 POST (1.1-1.3x YARP), and H3 POST (~1.0-1.1x YARP). H2 TLS→H2 TLS POST sustain is healthy on this pass (@ 41f4adee; TWP ~1.2-1.4x YARP).
Median of 3 repeats @ 41f4adee. Source: Actions 36853276459 (compare-post).
| Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain |
4,742 (134 MiB / 45.2% CPU) |
3,498 (100 MiB / 48.2% CPU) |
🥇 5,044 (83 MiB / 41.4% CPU) |
4,956 (132 MiB / 42.0% CPU) |
3,176 (176 MiB / 55.5% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 3,056 (217 MiB / 47.3% CPU) |
1,354 (112 MiB / 21.0% CPU) |
1,818 (82 MiB / 23.9% CPU) |
0 (peak 2,979 · 143 MiB / 23.9% CPU) |
2,533 (168 MiB / 48.1% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,878 (220 MiB / 44.2% CPU) |
463 (109 MiB / 24.9% CPU) |
1,991 (90 MiB / 33.1% CPU) |
0 (126 MiB / 0.1% CPU) |
2,593 (243 MiB / 49.2% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 5,628 (228 MiB / 44.0% CPU) |
2,396 (96 MiB / 24.1% CPU) |
2,567 (68 MiB / 23.2% CPU) |
4,030 (peak 4,604 · 118 MiB / 21.6% CPU) |
4,340 (160 MiB / 47.3% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain | 🥇 3,268 (159 MiB / 41.6% CPU) |
Not possible |
1,340 (86 MiB / 24.0% CPU) |
2,854 (142 MiB / 23.5% CPU) |
2,389 (179 MiB / 46.1% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS | 🥇 2,330 (148 MiB / 40.5% CPU) |
Not possible |
1,010 (80 MiB / 23.1% CPU) |
2,062 (146 MiB / 22.9% CPU) |
1,903 (170 MiB / 46.3% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 2,132 (222 MiB / 43.9% CPU) |
Not possible |
1,136 (96 MiB / 31.5% CPU) |
0 (126 MiB / 0.1% CPU) |
1,800 (250 MiB / 47.3% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS | 🥇 2,139 (232 MiB / 44.8% CPU) |
368 (120 MiB / 25.0% CPU) |
1,669 (96 MiB / 36.1% CPU) |
0 (125 MiB / 0.1% CPU) |
1,973 (255 MiB / 48.6% CPU) |
Linux nginx H1/H2/H3 POST completed (nginx.org mainline). TWP÷YARP H1 ≈ 1.5×; H2→H1 ≈ 1.2×; H3 ≈ 1.1×. TWP÷nginx H3 ≈ 6×.
Userspace 5 ms one-way delay + 1% TCP connection stall (H1/H2); UDP is loss% only (no per-datagram delay; MsQuic-safe) + 1% datagram drop (H3); 64 KiB GET. Median of 3 repeats on windows-latest @ 41f4adee — 36853280515 (compare-lossy).
Not possible: HAProxy and Envoy columns are omitted (no official Windows port).
| Client | Origin | TWP | nginx | YARP |
|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain | 🥇 663 (88 MiB / 3.9% CPU) |
650 (142 MiB / 17.5% CPU) |
662 (118 MiB / 5.0% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 59 (peak 85 · 112 MiB / 1.6% CPU) |
18 (142 MiB / 0.6% CPU) |
18 (86 MiB / 0.9% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain | 🥇 1,123 (124 MiB / 16.9% CPU) |
Not possible (no QUIC) |
969 (188 MiB / 22.0% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 61 (peak 87 · 106 MiB / 1.6% CPU) |
18 (127 MiB / 0.1% CPU) |
17 (78 MiB / 0.9% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain |
16 (79 MiB / 0.9% CPU) |
Not possible | 🥇 18 (85 MiB / 1.4% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS |
16 (78 MiB / 0.7% CPU) |
Not possible | 🥇 18 (84 MiB / 1.4% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS |
1,042 (121 MiB / 16.1% CPU) |
Not possible | 🥇 1,244 (192 MiB / 29.5% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
998 (132 MiB / 16.4% CPU) |
Not possible (no QUIC) | 🥇 1,217 (185 MiB / 30.3% CPU) |
H1 is near parity with YARP. H2 HOL and H3 loss sustain are non-zero on this Windows GHA pass (@ 41f4adee); see table.
Median of 3 repeats @ 41f4adee. Source: 36853280515 (compare-lossy; lossy H3 uses quic-http3, UDP drop-only).
| Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|
| HTTP/1 · TLS | HTTP/1 · plain |
1,214 (120 MiB / 10.1% CPU) |
🥇 1,220 (102 MiB / 8.0% CPU) |
1,219 (83 MiB / 4.8% CPU) |
1,205 (128 MiB / 6.4% CPU) |
1,209 (148 MiB / 12.9% CPU) |
| HTTP/2 · TLS | HTTP/1 · plain | 🥇 315 (177 MiB / 5.2% CPU) |
40 (101 MiB / 0.2% CPU) |
40 (84 MiB / 0.2% CPU) |
40 (137 MiB / 0.3% CPU) |
40 (127 MiB / 1.1% CPU) |
| HTTP/3 · QUIC | HTTP/1 · plain |
1,516 (165 MiB / 21.2% CPU) |
🥇 1,570 (117 MiB / 23.2% CPU) |
1,338 (87 MiB / 18.7% CPU) |
1,462 (138 MiB / 24.5% CPU) |
1,510 (228 MiB / 30.5% CPU) |
| HTTP/2 · plain | HTTP/1 · plain | 🥇 329 (185 MiB / 5.3% CPU) |
40 (78 MiB / 0.2% CPU) |
41 (69 MiB / 0.1% CPU) |
40 (127 MiB / 0.3% CPU) |
40 (123 MiB / 0.9% CPU) |
| HTTP/2 · TLS | HTTP/2 · plain |
40 (111 MiB / 0.7% CPU) |
Not possible |
40 (85 MiB / 0.3% CPU) |
40 (135 MiB / 0.3% CPU) |
🥇 41 (130 MiB / 1.3% CPU) |
| HTTP/2 · TLS | HTTP/2 · TLS |
40 (108 MiB / 0.9% CPU) |
Not possible | 🥇 42 (87 MiB / 0.3% CPU) |
40 (137 MiB / 0.3% CPU) |
41 (130 MiB / 1.2% CPU) |
| HTTP/3 · QUIC | HTTP/2 · TLS | 🥇 1,477 (163 MiB / 23.1% CPU) |
Not possible |
1,212 (93 MiB / 21.4% CPU) |
1,379 (140 MiB / 25.8% CPU) |
1,417 (235 MiB / 31.4% CPU) |
| HTTP/3 · QUIC | HTTP/1 · TLS |
1,351 (169 MiB / 21.9% CPU) |
🥇 1,452 (120 MiB / 23.7% CPU) |
1,289 (92 MiB / 20.8% CPU) |
1,404 (138 MiB / 25.9% CPU) |
1,384 (213 MiB / 30.8% CPU) |
TWP H2 HOL ≫ YARP (~7.8×). H3 TWP÷YARP ≈ 1.07×.
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 @ 41f4adee (36853288521). 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 (91 MiB / 5.3% CPU) |
220 (144 MiB / 24.7% CPU) |
241 (111 MiB / 5.3% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/1 · plain |
256 (118 MiB / 3.6% CPU) |
230 (141 MiB / 24.4% CPU) |
🥇 256 (114 MiB / 5.3% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 282 (104 MiB / 12.3% CPU) |
Not possible (no QUIC) |
276 (173 MiB / 14.6% CPU) |
| Early response (origin writes after first request chunk) | HTTP/1 · TLS | HTTP/1 · plain | 🥇 13,378 (98 MiB / 42.5% CPU) |
723 (142 MiB / 24.9% CPU) |
7,476 (137 MiB / 50.0% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/1 · plain | 🥇 3,367 (202 MiB / 52.7% CPU) |
350 (143 MiB / 24.7% CPU) |
2,711 (136 MiB / 49.8% CPU) |
| Early response (origin writes after first request chunk) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,777 (153 MiB / 42.6% CPU) |
Not possible (no QUIC) |
2,477 (213 MiB / 52.8% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · plain | HTTP/1 · plain |
248 (108 MiB / 3.2% CPU) |
250 (127 MiB / 9.2% CPU) |
🥇 256 (105 MiB / 4.1% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 256 (123 MiB / 3.5% CPU) |
Not possible |
256 (142 MiB / 6.1% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 3,606 (118 MiB / 30.4% CPU) |
Not possible |
21 (103 MiB / 0.3% CPU) |
| Duplex (both directions live) | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 3,531 (119 MiB / 29.9% CPU) |
Not possible |
15 (109 MiB / 0.3% CPU) |
| Duplex (WebSocket / H1 Upgrade) | HTTP/1 · TLS | HTTP/1 · plain | 🥇 69,940 (98 MiB / 44.0% CPU) |
38,586 (143 MiB / 24.6% CPU) |
65,385 (90 MiB / 45.1% CPU) |
| Scenario | Client | Origin | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|---|---|
| Slow consumer (256 KiB GET, throttled client read) | HTTP/1 · TLS | HTTP/1 · plain |
463 (119 MiB / 9.7% CPU) |
404 (100 MiB / 9.7% CPU) |
466 (84 MiB / 5.9% CPU) |
🥇 472 (136 MiB / 6.4% CPU) |
417 (146 MiB / 14.6% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/1 · plain | 🥇 474 (137 MiB / 19.9% CPU) |
468 (100 MiB / 23.1% CPU) |
473 (84 MiB / 9.3% CPU) |
468 (154 MiB / 7.5% CPU) |
466 (144 MiB / 23.9% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 473 (130 MiB / 34.8% CPU) |
120 (peak 342 · 94 MiB / 5.8% CPU) |
472 (88 MiB / 10.5% CPU) |
1 (144 MiB / 0.2% CPU) |
472 (195 MiB / 37.8% CPU) |
| Early response (origin writes after first request chunk) | HTTP/1 · TLS | HTTP/1 · plain |
5,001 (140 MiB / 45.5% CPU) |
0 (peak 3,541 · 102 MiB / 34.1% CPU) |
🥇 5,150 (83 MiB / 41.6% CPU) |
0 (peak 2,904 · 129 MiB / 25.5% CPU) |
3,344 (177 MiB / 55.4% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/1 · plain | 🥇 2,430 (218 MiB / 48.5% CPU) |
0 (peak 1,345 · 112 MiB / 24.1% CPU) |
1,363 (84 MiB / 12.8% CPU) |
0 (peak 2,354 · 153 MiB / 21.4% CPU) |
2,167 (168 MiB / 48.6% CPU) |
| Early response (origin writes after first request chunk) | HTTP/3 · QUIC | HTTP/1 · plain | 🥇 2,939 (193 MiB / 45.7% CPU) |
0 (peak 442 · 116 MiB / 24.8% CPU) |
1,956 (91 MiB / 34.3% CPU) |
0 (126 MiB / 0.1% CPU) |
2,141 (253 MiB / 48.4% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · plain | HTTP/1 · plain |
478 (142 MiB / 16.7% CPU) |
463 (79 MiB / 11.7% CPU) |
🥇 480 (68 MiB / 6.7% CPU) |
474 (146 MiB / 4.7% CPU) |
468 (147 MiB / 15.5% CPU) |
| Slow consumer (256 KiB GET, throttled client read) | HTTP/2 · TLS | HTTP/2 · TLS |
452 (144 MiB / 21.1% CPU) |
Not possible |
461 (88 MiB / 19.4% CPU) |
460 (152 MiB / 12.4% CPU) |
🥇 470 (162 MiB / 29.9% CPU) |
| Early response (origin writes after first request chunk) | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 1,936 (peak 2,226 · 141 MiB / 35.4% CPU) |
Not possible |
1,147 (84 MiB / 23.2% CPU) |
0 (peak 2,192 · 151 MiB / 21.8% CPU) |
50 (145 MiB / 1.4% CPU) |
| Duplex (both directions live) | HTTP/2 · TLS | HTTP/2 · TLS | 🥇 1,934 (141 MiB / 35.7% CPU) |
Not possible |
256 (86 MiB / 4.8% CPU) |
0 (peak 2,083 · 156 MiB / 21.4% CPU) |
6 (150 MiB / 0.4% CPU) |
| Duplex (WebSocket / H1 Upgrade) | HTTP/1 · TLS | HTTP/1 · plain |
34,436 (124 MiB / 44.9% CPU) |
37,247 (100 MiB / 36.4% CPU) |
🥇 37,608 (83 MiB / 39.8% CPU) |
36,405 (127 MiB / 41.2% CPU) |
31,719 (125 MiB / 44.9% CPU) |
Slow consumer is sleep-bound; H1/H2/H3 sit in the same band. Early-response H1: TWP leads (~1.79x / 1.50x YARP Win/Linux). Duplex H2 sustain is non-zero for TWP on this GHA pass (@ 41f4adee). WebSocket: TWP is ~1.07x YARP on Windows; on Linux HAProxy and nginx lead and TWP is ~1.09x YARP.
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 @ 41f4adee. Source: Actions 36853284423. 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 | 🥇 20,894 (84 MiB / 48.0% CPU) |
8,845 (142 MiB / 24.6% CPU) |
17,746 (101 MiB / 47.9% CPU) |
| New-connection · tiny GET | 🥇 724 (80 MiB / 1.5% CPU) |
248 (141 MiB / 1.4% CPU) |
717 (112 MiB / 0.5% CPU) |
| Keep-alive · 256 KiB GET | 🥇 2,843 (117 MiB / 47.5% CPU) |
170 (142 MiB / 24.6% CPU) |
2,663 (129 MiB / 47.1% CPU) |
Median of 3 repeats @ 41f4adee. Source: Actions 36853284423.
| Workload | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|
| Keep-alive · tiny GET |
25,285 (107 MiB / 50.0% CPU) |
🥇 29,752 (99 MiB / 42.2% CPU) |
29,386 (84 MiB / 43.6% CPU) |
18,088 (127 MiB / 58.7% CPU) |
21,832 (134 MiB / 51.1% CPU) |
| New-connection · tiny GET |
1,028 (117 MiB) |
1,080 (101 MiB / 0.7% CPU) |
1,014 (82 MiB / 0.2% CPU) |
🥇 1,155 (128 MiB) |
1,030 (147 MiB) |
| Keep-alive · 256 KiB GET |
2,886 (126 MiB / 37.0% CPU) |
1,817 (99 MiB / 53.2% CPU) |
🥇 3,135 (83 MiB / 33.6% CPU) |
2,879 (145 MiB / 33.9% CPU) |
2,252 (174 MiB / 45.2% CPU) |
Pending re-measure on macos-15 (Apple Silicon M1, 3-core / 7 GB): cells below read Not measured until the Mac shards finish.
| Workload | TWP | nginx | HAProxy | Envoy | YARP |
|---|---|---|---|---|---|
| Keep-alive · tiny GET | Not measured | Not measured | Not measured | Not measured | Not measured |
| New-connection · tiny GET | Not measured | Not measured | Not measured | Not measured | Not measured |
| Keep-alive · 256 KiB GET | Not measured | Not measured | Not measured | Not measured | Not measured |
On Windows, TWP leads YARP on keep-alive tiny (1.18x) and keep-alive 256 KiB (1.07x); new-connection is a tie (1.01x). On Linux, nginx and HAProxy lead keep-alive tiny, Envoy leads new-connection, and HAProxy leads keep-alive 256 KiB; TWP stays ahead of YARP on keep-alive and ties on new-connection (1.00x). macOS Apple Silicon numbers are pending re-measure. New-connection is Darwin SslStream-bound (TWP≈YARP; handshake p99 SLO 500 ms on macOS only — Win/Linux stay at 200 ms).
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 @ 41f4adee — 36853254836.
| OS | Titanium | YARP | nginx | HAProxy | Envoy |
|---|---|---|---|---|---|
| Windows | 🥇 52,009 (96 MiB / 22.7% CPU) |
31,057 (126 MiB / 45.5% CPU) |
0 (143 MiB / 24.9% CPU) |
Not possible | Not possible |
| Linux | 🥇 41,339 (117 MiB / 30.4% CPU) |
24,277 (158 MiB / 41.9% CPU) |
0 (114 MiB / 24.8% CPU) |
8,952 (83 MiB / 24.4% CPU) |
14,753 (127 MiB / 20.7% CPU) |
| macOS | Not measured | Not measured | Not measured | Not measured | Not measured |
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 @ 41f4adee — 36853254836.
Not possible: nginx column omitted (no H2 upstream).
| OS | Titanium | YARP | HAProxy | Envoy |
|---|---|---|---|---|
| Windows | 🥇 61,643 (90 MiB / 23.5% CPU) |
36,011 (116 MiB / 48.1% CPU) |
Not possible | Not possible |
| Linux | 🥇 43,166 (121 MiB / 29.6% CPU) |
24,252 (161 MiB / 43.4% CPU) |
7,782 (83 MiB / 24.5% CPU) |
12,044 (127 MiB / 22.2% CPU) |
| macOS | Not measured | Not measured | Not measured | Not measured |
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 | 🥇 22,332 (101 MiB / 47.4% CPU) |
21,366 (93 MiB / 43.1% CPU) |
9,361 (142 MiB / 24.6% CPU) |
Not possible | Not possible |
| Linux |
25,321 (141 MiB / 43.7% CPU) |
22,634 (134 MiB / 44.1% CPU) |
26,985 (103 MiB / 37.3% CPU) |
26,853 (83 MiB / 39.7% CPU) |
🥇 28,050 (127 MiB / 37.9% CPU) |
| macOS | Not measured | Not measured | Not measured | Not measured | Not measured |
WebSocket echo over RFC 8441 extended CONNECT (H2 TLS client → H1 plain origin). Mode: compare-ws-h2 (*-duplex-ws-h2).
Linux on this row (~1.5k, ~9% CPU) was measured with the probe client leaving Nagle on and splitting each HTTP/2 frame into two TLS records. That matches a ~40 ms delayed ACK, and HAProxy landed at the same rate. The probe now sets NoDelay and writes the frame in one record. Treat the Linux numbers below as the pre-fix harness until a re-measure replaces them. The change is in the client, so every peer on this row moves together.
Not possible: nginx column omitted (no RFC 8441 extended CONNECT).
| OS | Titanium | YARP | HAProxy | Envoy |
|---|---|---|---|---|
| Windows | 🥇 23,836 (168 MiB / 44.5% CPU) |
0 (167 MiB / 19.6% CPU) |
Not possible | Not possible |
| Linux |
1,532 (146 MiB / 9.2% CPU) |
0 (174 MiB / 19.4% CPU) |
🥇 1,537 (83 MiB / 3.1% CPU) |
0 (122 MiB / 0.3% CPU) |
| macOS | Not measured | Not measured | Not measured | Not measured |
| 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.40 and Full÷Reverse ≥ 0.40 (median of 3 GHA runs @ c=64). Absolute RPS moves with runner heat; ratios are the claim.
- Do not enable the HTTP/2 MITM multi-origin relay pool (
MaxOriginHttp2ConnectionsPerAuthority> 1 under interception): Tip A/B showed Lite err%~29 and RSS blow-up, which fails the Lite/Full÷Reverse floors. Multi-origin remains gate-off compressed-relay only. - 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).