Skip to content
github-actions[bot] edited this page Aug 29, 2026 · 210 revisions

Performance

Titanium targets low-overhead MITM proxying: connection pooling, HTTP/2 multiplexing, and buffer reuse. Numbers below are Release measurements with RpsLoadProbe (and BenchmarkDotNet / Basic example where noted). Publishable tables cite GitHub Actions medians on matched 4 vCPU / 16 GiB runners. Absolute RPS still varies by OS kernel, TLS, and MsQuic packaging — compare within a table, not across Windows vs Linux.

Control arms: nginx (native C reverse-proxy ceiling; Linux is authoritative) and YARP (Yarp.ReverseProxy, managed .NET reverse proxy). Neither can MITM (no CONNECT / forged certs). FiddlerCore is not compared (commercial debugger license; not a throughput peer).

For pooling knobs and certificate first-visit tuning, see Performance and pooling. For the local cool A/B lab, laptop tables, and profiling notes, see Performance Profiling.

Contents

Measurement environment

Both OS use the standard public-repo GitHub-hosted runner class (4 vCPU / 16 GiB / 14 GB SSD). Same harness knobs (workflow_dispatch RPS saturation: warmup 2s / measure 8s; concurrency 8, 16, 32, 64; median of 3 repeats; --stop-on-slo-fail default on). Every --ramp arm is three OS processes (parent load generator + origin child + proxy child), except origin-direct arms (load gen + origin only). Prefer TWP÷YARP / TWP÷nginx ratios over absolute RPS.

Laptop High-perf / cool-paired Windows numbers live on Performance Local Lab. Do not mix those absolutes into the tables below.

Tiered cadence

Tier Mode When
Daily / per-PR compare-spot minutes
Milestone compare-terminate / compare-matrix ~1–2h
Editions compare-editions ~60 min (CLI / Plus / Intercept stress arms)
Cross-version (Gate 2) compare-cross-version ~1–2h vs committed 6.0 baselines
Release / wiki compare-product ~3–4h
Heavier tables compare-bodies / post / lossy / arch / bridges / tls-cost dispatch independently

See PERF-GATES.md.

Windows (GitHub-hosted windows-latest)

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)
YARP Yarp.ReverseProxy 2.3.0
Harness RpsLoadProbe Release; median of 3 repeats

Linux (GitHub-hosted ubuntu-latest)

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)
YARP Yarp.ReverseProxy 2.3.0
Harness RpsLoadProbe Release; median of 3 repeats where noted

Saturation control

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 @ 9d7c2966 — 32866709227. 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). H3→H1 (saturation Block C): Win TWP RSS 103 MiB vs YARP 119 (0.87×); Linux 142 vs 181 (0.78×). RPS vs YARP (0.99× / 1.1×).

pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-saturation

Block A — H1 plain

Arm Generator Notes
origin-direct dotnet-httpclient No proxy — origin ceiling under the same client used for publishable ratios
origin-direct-bombardier bombardier External client check (CI installs bombardier)
bare-reverse-http1 dotnet-httpclient Thin C# H1 reverse (BareHttp1ReverseProxy) — .NET runtime / loopback ceiling for a three-process reverse hop; not a product peer
nginx-reverse-http1 / yarp-reverse-http1 / twp-reverse-http1 dotnet-httpclient Product peers (medals among these three only)

Windows (windows-latest)

Arm Generator Sustain Peak % of origin-HttpClient
origin-direct dotnet-httpclient 50,717
(53 MiB / 41.6% CPU)
50,717
(53 MiB / 41.6% CPU)
100.0%
origin-direct-bombardier bombardier 38,335
(55 MiB / 22.7% CPU)
38,335
(55 MiB / 22.7% CPU)
75.6%
bare-reverse-http1 dotnet-httpclient 25,467
(55 MiB / 46.5% CPU)
25,467
(55 MiB / 46.5% CPU)
50.2%
nginx-reverse-http1 dotnet-httpclient 13,259
(121 MiB / 25.0% CPU)
13,422
(121 MiB / 25.0% CPU)
26.5%
yarp-reverse-http1 dotnet-httpclient 21,331
(89 MiB / 49.9% CPU)
21,331
(89 MiB / 49.9% CPU)
42.1%
twp-reverse-http1 dotnet-httpclient 🥇 25,358
(75 MiB / 49.2% CPU)
25,358
(75 MiB / 49.2% CPU)
50.0%

Linux (ubuntu-latest)

Arm Generator Sustain Peak % of origin-HttpClient
origin-direct dotnet-httpclient 68,992
(78 MiB / 41.8% CPU)
68,992
(78 MiB / 41.8% CPU)
100.0%
origin-direct-bombardier bombardier 41,996
(78 MiB / 34.4% CPU)
41,996
(78 MiB / 34.4% CPU)
60.9%
bare-reverse-http1 dotnet-httpclient 32,077
(60 MiB / 45.0% CPU)
32,077
(60 MiB / 45.0% CPU)
46.5%
nginx-reverse-http1 dotnet-httpclient 🥇 38,039
(72 MiB / 40.9% CPU)
38,039
(72 MiB / 40.9% CPU)
55.1%
yarp-reverse-http1 dotnet-httpclient 26,736
(113 MiB / 49.2% CPU)
26,736
(113 MiB / 49.2% CPU)
38.8%
twp-reverse-http1 dotnet-httpclient 31,431
(84 MiB / 50.0% CPU)
31,431
(84 MiB / 50.0% CPU)
45.6%

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).

Block B — H2 TLS→H1

Peer ratios (÷YARP / ÷nginx) on median peak; RPS cells embed (MiB / CPU%).

Windows (windows-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 8,264
(248 MiB / 24.8% CPU)
8,411
(248 MiB / 24.8% CPU)
0.29× 1×
yarp-reverse-http2 dotnet-httpclient 29,096
(95 MiB / 53.0% CPU)
29,096
(95 MiB / 53.0% CPU)
1× 3.46×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 35,180
(96 MiB / 52.0% CPU)
35,180
(96 MiB / 52.0% CPU)
1.21× 4.18×

Linux (ubuntu-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http2 dotnet-httpclient 14,490
(97 MiB / 19.4% CPU)
14,490
(97 MiB / 19.4% CPU)
0.51× 1×
yarp-reverse-http2 dotnet-httpclient 28,448
(120 MiB / 49.0% CPU)
28,448
(120 MiB / 49.0% CPU)
1× 1.96×
twp-reverse-http2-cleartext dotnet-httpclient 🥇 33,751
(120 MiB / 51.7% CPU)
33,751
(120 MiB / 51.7% CPU)
1.19× 2.33×

Block C — H3→H1

Same layout as Block B. Requires QuicListener; nginx only with http_v3_module (Windows nginx has no QUIC).

Windows (windows-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient Not possible (no QUIC) Not possible — —
yarp-reverse-http3-cleartext dotnet-httpclient 14,549
(119 MiB / 50.8% CPU)
15,166
(119 MiB / 50.8% CPU)
1× —
twp-reverse-http3-cleartext dotnet-httpclient 🥇 15,028
(103 MiB / 46.5% CPU)
15,028
(103 MiB / 46.5% CPU)
0.99× —

Linux (ubuntu-latest)

Arm Generator Sustain Peak ÷YARP ÷nginx
nginx-reverse-http3-cleartext dotnet-httpclient 0
(103 MiB / 22.4% CPU)
15,186
(103 MiB / 22.4% CPU)
0.85× 1×
yarp-reverse-http3-cleartext dotnet-httpclient 17,856
(181 MiB / 49.5% CPU)
17,856
(181 MiB / 49.5% CPU)
1× 1.18×
twp-reverse-http3-cleartext dotnet-httpclient 🥇 20,378
(142 MiB / 50.4% CPU)
20,378
(142 MiB / 50.4% CPU)
1.14× 1.34×

How to read the tables

  • Reverse = bare transparent fixed-forward (no TWP plugins / interception). nginx knobs match TWP/YARP streaming (keepalive 256, proxy_buffering off). MITM = TWP-only table on the same Client×Origin wires: Lite = no-op handlers (unchanged-lite finish reuses reverse compressed relay); Full = mutating handlers that append up to four unique headers per direction (RPS harness adds one; product uses MitmCompressedRelayHelper — no probe name in library code). Remove/replace/non-unique header growth and body mutation still force full decode/re-encode. nginx/YARP cannot MITM. HTTP/3 has no cleartext client (QUIC always encrypted).
  • Sustainable = last concurrency that still met error/latency SLOs. Peak = highest RPS in that ramp.
  • 🥇 = best among TWP / nginx / YARP on Reverse rows (or saturation blocks): highest RPS; on an RPS tie, lower Memory (RSS) then lower CPU%. MITM is TWP-only. Lite÷Reverse / Full÷Reverse = TWP MITM lite or full sustain ÷ TWP Reverse sustain on the same Client×Origin from the same compare-product job.
  • Not possible = product cannot do that path. Not measured = path exists but no published number yet for that OS.
  • Product refresh: compare-product @ acfb27e1 — 33087088466. Heavier/saturation/tls:
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-product
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-bodies
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-post
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-lossy
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-tls-cost
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-arch
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-saturation

Windows — Titanium vs nginx vs YARP

Client / origin: HTTP version and whether TLS is used (plain = cleartext, TLS = encrypted, QUIC = HTTP/3).

Reverse

Median of 3 repeats on windows-latest (4 vCPU / 16 GiB). Bare reverse 5×5 @ acfb27e1 — compare-product 33087088466. 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. Laptop High-perf / cool-paired numbers stay on the local lab.

Load generators: Reverse inbound H3 arms use dotnet-httpclient (http_version=3.0, RequestVersionExact). nginx/Windows is same-OS only (no QUIC).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · plain HTTP/1 · plain 🥇 32525
(72 MiB / 48.2% CPU)
🥇 32525
(72 MiB / 48.2% CPU)
19609
(120 MiB / 24.7% CPU)
19609
(120 MiB / 24.7% CPU)
27504
(87 MiB / 49.3% CPU)
27504
(87 MiB / 49.3% CPU)
HTTP/1 · plain HTTP/1 · TLS 🥇 27254
(84 MiB / 50.5% CPU)
🥇 27254
(84 MiB / 50.5% CPU)
Not possible Not possible 24546
(98 MiB / 50.4% CPU)
24546
(98 MiB / 50.4% CPU)
HTTP/1 · plain HTTP/2 · plain 🥇 43011
(101 MiB / 48.9% CPU)
🥇 43011
(101 MiB / 48.9% CPU)
Not possible Not possible 40913
(88 MiB / 50.4% CPU)
40913
(88 MiB / 50.4% CPU)
HTTP/1 · plain HTTP/2 · TLS 🥇 38231
(107 MiB / 47.1% CPU)
🥇 38231
(107 MiB / 47.1% CPU)
Not possible Not possible 36573
(101 MiB / 48.8% CPU)
36573
(101 MiB / 48.8% CPU)
HTTP/1 · plain HTTP/3 · QUIC 21174
(109 MiB / 49.2% CPU)
21174
(109 MiB / 49.2% CPU)
Not possible (no QUIC) Not possible (no QUIC) 🥇 21494
(119 MiB / 50.7% CPU)
🥇 21494
(119 MiB / 50.7% CPU)
HTTP/1 · TLS HTTP/1 · plain 🥇 26461
(83 MiB / 46.9% CPU)
🥇 26461
(83 MiB / 46.9% CPU)
12992
(137 MiB / 24.7% CPU)
12992
(137 MiB / 24.7% CPU)
23267
(103 MiB / 49.5% CPU)
23267
(103 MiB / 49.5% CPU)
HTTP/1 · TLS HTTP/1 · TLS 🥇 24189
(83 MiB / 47.1% CPU)
🥇 24189
(83 MiB / 47.1% CPU)
Not possible Not possible 21332
(101 MiB / 48.5% CPU)
21332
(101 MiB / 48.5% CPU)
HTTP/1 · TLS HTTP/2 · plain 🥇 33986
(111 MiB / 47.9% CPU)
🥇 33986
(111 MiB / 47.9% CPU)
Not possible Not possible 33398
(108 MiB / 48.7% CPU)
33398
(108 MiB / 48.7% CPU)
HTTP/1 · TLS HTTP/2 · TLS 🥇 30890
(113 MiB / 46% CPU)
🥇 30890
(113 MiB / 46% CPU)
Not possible Not possible 30843
(106 MiB / 48.1% CPU)
30843
(106 MiB / 48.1% CPU)
HTTP/1 · TLS HTTP/3 · QUIC 🥇 19117
(113 MiB / 49.5% CPU)
🥇 19117
(113 MiB / 49.5% CPU)
Not possible (no QUIC) Not possible (no QUIC) 18913
(124 MiB / 51.2% CPU)
18913
(124 MiB / 51.2% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 42486
(98 MiB / 50.4% CPU)
🥇 42486
(98 MiB / 50.4% CPU)
Not possible Not possible 39198
(85 MiB / 49.8% CPU)
39198
(85 MiB / 49.8% CPU)
HTTP/2 · plain HTTP/1 · TLS 🥇 36446
(104 MiB / 50.9% CPU)
🥇 36446
(104 MiB / 50.9% CPU)
Not possible Not possible 33101
(92 MiB / 50% CPU)
33101
(92 MiB / 50% CPU)
HTTP/2 · plain HTTP/2 · plain 🥇 92750
(72 MiB / 36.4% CPU)
🥇 92750
(72 MiB / 36.4% CPU)
Not possible Not possible 69751
(93 MiB / 50.1% CPU)
69751
(93 MiB / 50.1% CPU)
HTTP/2 · plain HTTP/2 · TLS 🥇 80271
(81 MiB / 35.9% CPU)
🥇 80271
(81 MiB / 35.9% CPU)
Not possible Not possible 60779
(98 MiB / 49.5% CPU)
60779
(98 MiB / 49.5% CPU)
HTTP/2 · plain HTTP/3 · QUIC 🥇 32652
(130 MiB / 52.1% CPU)
🥇 32652
(130 MiB / 52.1% CPU)
Not possible (no QUIC) Not possible (no QUIC) 31495
(123 MiB / 51% CPU)
31495
(123 MiB / 51% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 40763
(101 MiB / 50.8% CPU)
🥇 40763
(101 MiB / 50.8% CPU)
11581
(137 MiB / 24.3% CPU)
11581
(137 MiB / 24.3% CPU)
35553
(91 MiB / 50.8% CPU)
35553
(91 MiB / 50.8% CPU)
HTTP/2 · TLS HTTP/1 · TLS 🥇 35437
(105 MiB / 51.9% CPU)
🥇 35437
(105 MiB / 51.9% CPU)
Not possible Not possible 31286
(97 MiB / 50.2% CPU)
31286
(97 MiB / 50.2% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 86433
(91 MiB / 36% CPU)
🥇 86433
(91 MiB / 36% CPU)
Not possible Not possible 59569
(103 MiB / 52.5% CPU)
59569
(103 MiB / 52.5% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 76135
(88 MiB / 35.1% CPU)
🥇 76135
(88 MiB / 35.1% CPU)
Not possible Not possible 53908
(98 MiB / 50% CPU)
53908
(98 MiB / 50% CPU)
HTTP/2 · TLS HTTP/3 · QUIC 🥇 32094
(134 MiB / 52.6% CPU)
🥇 32094
(134 MiB / 52.6% CPU)
Not possible (no QUIC) Not possible (no QUIC) 28945
(125 MiB / 53.4% CPU)
28945
(125 MiB / 53.4% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 19956
(107 MiB / 43.9% CPU)
🥇 19956
(107 MiB / 43.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) 17543
(163 MiB / 48.6% CPU)
17543
(163 MiB / 48.6% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 🥇 17585
(113 MiB / 43.1% CPU)
🥇 17585
(113 MiB / 43.1% CPU)
Not possible Not possible 17460
(163 MiB / 47.8% CPU)
17460
(163 MiB / 47.8% CPU)
HTTP/3 · QUIC HTTP/2 · plain 🥇 29131
(120 MiB / 47% CPU)
🥇 29131
(120 MiB / 47% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) 27165
(152 MiB / 48% CPU)
27165
(152 MiB / 48% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 27017
(123 MiB / 46.4% CPU)
🥇 27017
(123 MiB / 46.4% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) 25543
(169 MiB / 47.6% CPU)
25543
(169 MiB / 47.6% CPU)
HTTP/3 · QUIC HTTP/3 · QUIC 🥇 17500
(123 MiB / 45.3% CPU)
🥇 17500
(123 MiB / 45.3% CPU)
Not possible (no QUIC) Not possible (no QUIC) 14335
(166 MiB / 50.6% CPU)
14335
(166 MiB / 50.6% CPU)

MITM (TWP only)

Same Client×Origin wires with interception on (compare-product 33087088466). 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/YARP cannot MITM. Lite÷Reverse / Full÷Reverse vs bare reverse (same job). Completion gate: Lite and Full ≥ 0.70× reverse sustain @ c=64 (median of 3 GHA runs).

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. @ acfb27e1: 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 31797
(73 MiB / 49.6% CPU)
30751
(77 MiB / 48.5% CPU)
0.98× 0.95×
HTTP/1 · plain HTTP/1 · TLS 26384
(89 MiB / 50.2% CPU)
26276
(93 MiB / 50.3% CPU)
0.97× 0.96×
HTTP/1 · plain HTTP/2 · plain 41954
(117 MiB / 50.7% CPU)
40419
(118 MiB / 48.9% CPU)
0.98× 0.94×
HTTP/1 · plain HTTP/2 · TLS 37636
(114 MiB / 50.1% CPU)
36683
(115 MiB / 49.7% CPU)
0.98× 0.96×
HTTP/1 · plain HTTP/3 · QUIC 21606
(110 MiB / 51.7% CPU)
21080
(113 MiB / 51% CPU)
1.02× 1×
HTTP/1 · TLS HTTP/1 · plain 25850
(87 MiB / 49.7% CPU)
25148
(91 MiB / 46.9% CPU)
0.98× 0.95×
HTTP/1 · TLS HTTP/1 · TLS 23528
(92 MiB / 48.7% CPU)
23258
(92 MiB / 47.7% CPU)
0.97× 0.96×
HTTP/1 · TLS HTTP/2 · plain 32389
(125 MiB / 48.4% CPU)
32241
(125 MiB / 47.8% CPU)
0.95× 0.95×
HTTP/1 · TLS HTTP/2 · TLS 30316
(122 MiB / 49% CPU)
29985
(122 MiB / 45.7% CPU)
0.98× 0.97×
HTTP/1 · TLS HTTP/3 · QUIC 18714
(115 MiB / 51.5% CPU)
18546
(113 MiB / 50.8% CPU)
0.98× 0.97×
HTTP/2 · plain HTTP/1 · plain 40688
(97 MiB / 52.2% CPU)
40132
(92 MiB / 53.3% CPU)
0.96× 0.94×
HTTP/2 · plain HTTP/1 · TLS 35409
(103 MiB / 53.1% CPU)
34404
(101 MiB / 52% CPU)
0.97× 0.94×
HTTP/2 · plain HTTP/2 · plain 75435
(73 MiB / 50.5% CPU)
72871
(70 MiB / 50.7% CPU)
0.81× 0.79×
HTTP/2 · plain HTTP/2 · TLS 66584
(87 MiB / 47% CPU)
64779
(81 MiB / 46.6% CPU)
0.83× 0.81×
HTTP/2 · plain HTTP/3 · QUIC 32476
(126 MiB / 53.1% CPU)
31706
(126 MiB / 54% CPU)
0.99× 0.97×
HTTP/2 · TLS HTTP/1 · plain 39532
(101 MiB / 50.9% CPU)
38678
(102 MiB / 53.2% CPU)
0.97× 0.95×
HTTP/2 · TLS HTTP/1 · TLS 33965
(102 MiB / 53.4% CPU)
33418
(102 MiB / 52.3% CPU)
0.96× 0.94×
HTTP/2 · TLS HTTP/2 · plain 73247
(90 MiB / 48.3% CPU)
70400
(90 MiB / 48.2% CPU)
0.85× 0.81×
HTTP/2 · TLS HTTP/2 · TLS 65150
(89 MiB / 45.8% CPU)
62910
(92 MiB / 45.6% CPU)
0.86× 0.83×
HTTP/2 · TLS HTTP/3 · QUIC 31089
(137 MiB / 54.2% CPU)
31210
(139 MiB / 53.8% CPU)
0.97× 0.97×
HTTP/3 · QUIC HTTP/1 · plain 17969
(115 MiB / 45.6% CPU)
18180
(108 MiB / 44.7% CPU)
0.9× 0.91×
HTTP/3 · QUIC HTTP/1 · TLS 16297
(127 MiB / 44.8% CPU)
16040
(123 MiB / 45.9% CPU)
0.93× 0.91×
HTTP/3 · QUIC HTTP/2 · plain 27209
(128 MiB / 48.8% CPU)
26679
(130 MiB / 49.4% CPU)
0.93× 0.92×
HTTP/3 · QUIC HTTP/2 · TLS 25629
(136 MiB / 49.9% CPU)
25139
(139 MiB / 48.1% CPU)
0.95× 0.93×
HTTP/3 · QUIC HTTP/3 · QUIC 19028
(121 MiB / 46.2% CPU)
15493
(120 MiB / 47.4% CPU)
1.09× 0.89×

Linux — Titanium vs nginx vs YARP

Reverse

Median of 3 repeats on ubuntu-latest (4 vCPU / 16 GiB). Bare reverse 5×5 @ acfb27e1 — compare-product 33087088466. Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Linux nginx is the authoritative nginx baseline. nginx terminate peers use keepalive 256 + streaming buffers. The RPS workflow installs nginx.org mainline (http_v3_module) and libmsquic. Prefer ratios over absolute RPS.

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · plain HTTP/1 · plain 32755
(82 MiB / 50.3% CPU)
32755
(82 MiB / 50.3% CPU)
🥇 38827
(72 MiB / 41.2% CPU)
🥇 38827
(72 MiB / 41.2% CPU)
27943
(114 MiB / 49.8% CPU)
27943
(114 MiB / 49.8% CPU)
HTTP/1 · plain HTTP/1 · TLS 🥇 23666
(101 MiB / 50% CPU)
🥇 23666
(101 MiB / 50% CPU)
Not possible Not possible 21689
(129 MiB / 50.3% CPU)
21689
(129 MiB / 50.3% CPU)
HTTP/1 · plain HTTP/2 · plain 🥇 38108
(130 MiB / 51.6% CPU)
🥇 38108
(130 MiB / 51.6% CPU)
Not possible Not possible 35233
(126 MiB / 49.1% CPU)
35233
(126 MiB / 49.1% CPU)
HTTP/1 · plain HTTP/2 · TLS 🥇 31414
(136 MiB / 50.3% CPU)
🥇 31414
(136 MiB / 50.3% CPU)
Not possible Not possible 29740
(132 MiB / 47.8% CPU)
29740
(132 MiB / 47.8% CPU)
HTTP/1 · plain HTTP/3 · QUIC 🥇 22431
(133 MiB / 52.9% CPU)
🥇 22431
(133 MiB / 52.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) 21353
(150 MiB / 49.3% CPU)
21353
(150 MiB / 49.3% CPU)
HTTP/1 · TLS HTTP/1 · plain 23525
(104 MiB / 49.5% CPU)
23525
(104 MiB / 49.5% CPU)
🥇 28076
(97 MiB / 41.3% CPU)
🥇 28076
(97 MiB / 41.3% CPU)
20655
(135 MiB / 50.7% CPU)
20655
(135 MiB / 50.7% CPU)
HTTP/1 · TLS HTTP/1 · TLS 🥇 19570
(105 MiB / 48.4% CPU)
🥇 19570
(105 MiB / 48.4% CPU)
Not possible Not possible 17196
(133 MiB / 49.6% CPU)
17196
(133 MiB / 49.6% CPU)
HTTP/1 · TLS HTTP/2 · plain 🥇 27378
(146 MiB / 50.1% CPU)
🥇 27378
(146 MiB / 50.1% CPU)
Not possible Not possible 24990
(140 MiB / 49.2% CPU)
24990
(140 MiB / 49.2% CPU)
HTTP/1 · TLS HTTP/2 · TLS 🥇 23743
(148 MiB / 48.7% CPU)
🥇 23743
(148 MiB / 48.7% CPU)
Not possible Not possible 22430
(140 MiB / 47.7% CPU)
22430
(140 MiB / 47.7% CPU)
HTTP/1 · TLS HTTP/3 · QUIC 🥇 17521
(142 MiB / 51.9% CPU)
🥇 17521
(142 MiB / 51.9% CPU)
Not possible (no QUIC) Not possible (no QUIC) 16465
(158 MiB / 49.8% CPU)
16465
(158 MiB / 49.8% CPU)
HTTP/2 · plain HTTP/1 · plain 🥇 38052
(113 MiB / 53% CPU)
🥇 38052
(113 MiB / 53% CPU)
Not possible Not possible 34400
(112 MiB / 50.1% CPU)
34400
(112 MiB / 50.1% CPU)
HTTP/2 · plain HTTP/1 · TLS 🥇 28089
(120 MiB / 51.3% CPU)
🥇 28089
(120 MiB / 51.3% CPU)
Not possible Not possible 25844
(126 MiB / 50.3% CPU)
25844
(126 MiB / 50.3% CPU)
HTTP/2 · plain HTTP/2 · plain 🥇 65752
(99 MiB / 40.2% CPU)
🥇 65752
(99 MiB / 40.2% CPU)
Not possible Not possible 48901
(123 MiB / 46.8% CPU)
48901
(123 MiB / 46.8% CPU)
HTTP/2 · plain HTTP/2 · TLS 🥇 50899
(109 MiB / 39.7% CPU)
🥇 50899
(109 MiB / 39.7% CPU)
Not possible Not possible 39192
(124 MiB / 45.8% CPU)
39192
(124 MiB / 45.8% CPU)
HTTP/2 · plain HTTP/3 · QUIC 🥇 26614
(146 MiB / 50.6% CPU)
🥇 26614
(146 MiB / 50.6% CPU)
Not possible (no QUIC) Not possible (no QUIC) 26142
(148 MiB / 46.6% CPU)
26142
(148 MiB / 46.6% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 34556
(121 MiB / 52.1% CPU)
🥇 34556
(121 MiB / 52.1% CPU)
15326
(97 MiB / 19.3% CPU)
15326
(97 MiB / 19.3% CPU)
29570
(121 MiB / 50% CPU)
29570
(121 MiB / 50% CPU)
HTTP/2 · TLS HTTP/1 · TLS 🥇 26497
(118 MiB / 50.4% CPU)
🥇 26497
(118 MiB / 50.4% CPU)
Not possible Not possible 23247
(129 MiB / 50.1% CPU)
23247
(129 MiB / 50.1% CPU)
HTTP/2 · TLS HTTP/2 · plain 🥇 59314
(110 MiB / 39.9% CPU)
🥇 59314
(110 MiB / 39.9% CPU)
Not possible Not possible 39916
(123 MiB / 47% CPU)
39916
(123 MiB / 47% CPU)
HTTP/2 · TLS HTTP/2 · TLS 🥇 48903
(116 MiB / 39.4% CPU)
🥇 48903
(116 MiB / 39.4% CPU)
Not possible Not possible 34000
(126 MiB / 46% CPU)
34000
(126 MiB / 46% CPU)
HTTP/2 · TLS HTTP/3 · QUIC 🥇 25230
(147 MiB / 49.5% CPU)
🥇 25230
(147 MiB / 49.5% CPU)
Not possible (no QUIC) Not possible (no QUIC) 22753
(155 MiB / 46.6% CPU)
22753
(155 MiB / 46.6% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 20440
(143 MiB / 50.5% CPU)
🥇 20440
(143 MiB / 50.5% CPU)
0
(103 MiB / 22.5% CPU)
15123
(103 MiB / 22.5% CPU)
18428
(181 MiB / 50.1% CPU)
18428
(181 MiB / 50.1% CPU)
HTTP/3 · QUIC HTTP/1 · TLS 🥇 16026
(151 MiB / 47.7% CPU)
🥇 16026
(151 MiB / 47.7% CPU)
Not possible Not possible 15410
(192 MiB / 50.8% CPU)
15410
(192 MiB / 50.8% CPU)
HTTP/3 · QUIC HTTP/2 · plain 🥇 27106
(140 MiB / 53.8% CPU)
🥇 27106
(140 MiB / 53.8% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) 23921
(195 MiB / 48.7% CPU)
23921
(195 MiB / 48.7% CPU)
HTTP/3 · QUIC HTTP/2 · TLS 🥇 23376
(147 MiB / 51.8% CPU)
🥇 23376
(147 MiB / 51.8% CPU)
Not possible (no H3 to H2) Not possible (no H3 to H2) 21030
(191 MiB / 47.3% CPU)
21030
(191 MiB / 47.3% CPU)
HTTP/3 · QUIC HTTP/3 · QUIC 🥇 19912
(153 MiB / 46.1% CPU)
🥇 19912
(153 MiB / 46.1% CPU)
Not possible (no QUIC) Not possible (no QUIC) 15969
(193 MiB / 47.6% CPU)
15969
(193 MiB / 47.6% CPU)

MITM (TWP only)

Same Client×Origin wires with interception on (compare-product 33087088466). 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/YARP cannot MITM. Lite÷Reverse / Full÷Reverse vs bare reverse (same job). Completion gate: Lite and Full ≥ 0.70× reverse sustain @ c=64 (median of 3 GHA runs).

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. @ acfb27e1: 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 31444
(88 MiB / 50.6% CPU)
30821
(89 MiB / 50.9% CPU)
0.96× 0.94×
HTTP/1 · plain HTTP/1 · TLS 23894
(107 MiB / 51.2% CPU)
23188
(107 MiB / 51.4% CPU)
1.01× 0.98×
HTTP/1 · plain HTTP/2 · plain 37618
(136 MiB / 52.9% CPU)
37663
(137 MiB / 53% CPU)
0.99× 0.99×
HTTP/1 · plain HTTP/2 · TLS 30669
(142 MiB / 51.6% CPU)
29667
(145 MiB / 51.6% CPU)
0.98× 0.94×
HTTP/1 · plain HTTP/3 · QUIC 22046
(138 MiB / 53% CPU)
21676
(132 MiB / 53.6% CPU)
0.98× 0.97×
HTTP/1 · TLS HTTP/1 · plain 22746
(108 MiB / 50.5% CPU)
22497
(108 MiB / 50.7% CPU)
0.97× 0.96×
HTTP/1 · TLS HTTP/1 · TLS 19416
(110 MiB / 49.8% CPU)
18914
(111 MiB / 49% CPU)
0.99× 0.97×
HTTP/1 · TLS HTTP/2 · plain 26252
(156 MiB / 51.4% CPU)
25475
(158 MiB / 50.8% CPU)
0.96× 0.93×
HTTP/1 · TLS HTTP/2 · TLS 22934
(156 MiB / 49.7% CPU)
22165
(155 MiB / 50.2% CPU)
0.97× 0.93×
HTTP/1 · TLS HTTP/3 · QUIC 17465
(149 MiB / 53.1% CPU)
17074
(156 MiB / 52.9% CPU)
1× 0.97×
HTTP/2 · plain HTTP/1 · plain 36932
(117 MiB / 54.2% CPU)
35097
(114 MiB / 53.4% CPU)
0.97× 0.92×
HTTP/2 · plain HTTP/1 · TLS 28063
(123 MiB / 52.3% CPU)
26540
(128 MiB / 51.9% CPU)
1× 0.94×
HTTP/2 · plain HTTP/2 · plain 55285
(111 MiB / 49.6% CPU)
51175
(109 MiB / 48.9% CPU)
0.84× 0.78×
HTTP/2 · plain HTTP/2 · TLS 44079
(106 MiB / 47% CPU)
41560
(106 MiB / 45.9% CPU)
0.87× 0.82×
HTTP/2 · plain HTTP/3 · QUIC 26736
(142 MiB / 50.4% CPU)
26411
(146 MiB / 51% CPU)
1× 0.99×
HTTP/2 · TLS HTTP/1 · plain 34094
(117 MiB / 53.2% CPU)
33044
(122 MiB / 53.1% CPU)
0.99× 0.96×
HTTP/2 · TLS HTTP/1 · TLS 26666
(122 MiB / 51.8% CPU)
25291
(123 MiB / 51.5% CPU)
1.01× 0.95×
HTTP/2 · TLS HTTP/2 · plain 49372
(114 MiB / 48.6% CPU)
46927
(111 MiB / 47.3% CPU)
0.83× 0.79×
HTTP/2 · TLS HTTP/2 · TLS 40654
(113 MiB / 45.8% CPU)
39146
(113 MiB / 44.9% CPU)
0.83× 0.8×
HTTP/2 · TLS HTTP/3 · QUIC 24651
(151 MiB / 50.1% CPU)
24568
(151 MiB / 50.8% CPU)
0.98× 0.97×
HTTP/3 · QUIC HTTP/1 · plain 19354
(143 MiB / 51.3% CPU)
19096
(145 MiB / 51.7% CPU)
0.95× 0.93×
HTTP/3 · QUIC HTTP/1 · TLS 15012
(156 MiB / 48.7% CPU)
15042
(157 MiB / 49.2% CPU)
0.94× 0.94×
HTTP/3 · QUIC HTTP/2 · plain 25641
(158 MiB / 55.7% CPU)
25316
(157 MiB / 55.5% CPU)
0.95× 0.93×
HTTP/3 · QUIC HTTP/2 · TLS 22250
(153 MiB / 53.7% CPU)
22127
(159 MiB / 54.2% CPU)
0.95× 0.95×
HTTP/3 · QUIC HTTP/3 · QUIC 17601
(151 MiB / 48.8% CPU)
17265
(152 MiB / 48.4% CPU)
0.88× 0.87×

Editions (CLI / Plus / Intercept)

Note: twp-reverse-http1 and other library rows use Core with probe-tuned settings (no logging, no Via header, probe-warmed certs). Edition rows use titanium run -c twp.yaml product defaults — prefer the ÷baseline ratio column over absolute RPS. Inspector GUI is not spawnable in the harness; session-path overhead is twp-cli-intercept-http1 (route RequestHeaderSet transform).

Gates prioritize SLO survival under load and reasonable overhead vs baseline. Thresholds include noise headroom — see PERF-GATES.md.

Median of 3 repeats @ db4beb21. Source: Actions 33248789301 (compare-editions). Warmup 2s / measure 8s; concurrency 8–64; sustain = median peak RPS among SLO-pass steps @ c=64. RPS cells include (MiB / CPU%) at that step.

pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-editions
pwsh tools/RpsLoadProbe/validate-edition-gates.ps1 -CsvPath tools/RpsLoadProbe/results/rps-ramp-*.csv
Arm Win sustain Win peak Linux sustain Linux peak Win÷ Lin÷ Gate
twp-cli-reverse-http1 vs library 32502
(118 MiB / 48.1% CPU)
32656 61974
(212 MiB / 49.2% CPU)
62743 1.01× 1.05× ≥ 0.80×
twp-cli-reverse-http1-tls vs library TLS 26636
(140 MiB / 46.6% CPU)
26948 48754
(238 MiB / 48.4% CPU)
48777 1.00× 1.02× ≥ 0.80×
twp-cli-reverse-http1-route vs CLI 32482
(119 MiB / 44.4% CPU)
32699 61567
(210 MiB / 49.3% CPU)
61632 1.00× 0.99× ≥ 0.90×
twp-cli-plus-base-http1 vs CLI 32769
(122 MiB / 50.0% CPU)
32930 60478
(214 MiB / 48.4% CPU)
61447 1.01× 0.98× ≥ 0.90×
twp-cli-plus-cache-http1 (cold) vs CLI 22936
(126 MiB / 54.8% CPU)
23182 42808
(231 MiB / 57.7% CPU)
43086 0.71× 0.69× ≥ 0.60×
twp-cli-intercept-http1 vs CLI 25620
(123 MiB / 55.7% CPU)
25747 49909
(230 MiB / 52.0% CPU)
50061 0.79× 0.81× ≥ 0.65×
twp-cli-plus-waf-http1 vs CLI 27500
(125 MiB / 51.6% CPU)
27528 52781
(229 MiB / 50.4% CPU)
53216 0.85× 0.85× ≥ 0.70×
twp-cli-plus-cidr-http1 vs CLI 27841
(119 MiB / 50.5% CPU)
27861 52579
(223 MiB / 50.1% CPU)
54431 0.86× 0.85× ≥ 0.70×
twp-cli-plus-jwt-http1 vs CLI 16518
(143 MiB / 63.7% CPU)
16593 29979
(256 MiB / 66.3% CPU)
30141 0.51× 0.48× ≥ 0.45×
twp-cli-plus-ratelimit-http1 vs CLI 27845
(125 MiB / 51.2% CPU)
27919 53159
(228 MiB / 50.2% CPU)
53263 0.86× 0.86× ≥ 0.70×
twp-cli-plus-resilience-http1 vs CLI 32615
(127 MiB / 46.9% CPU)
32784 61074
(219 MiB / 48.7% CPU)
62983 1.00× 0.99× ≥ 0.65×
twp-cli-plus-discovery-file-http1 vs CLI 32620
(125 MiB / 46.8% CPU)
32756 61475
(216 MiB / 48.2% CPU)
62196 1.00× 0.99× ≥ 0.70×
twp-cli-plus-metrics-scrape-http1 vs CLI 32724
(122 MiB / 48.2% CPU)
32862 61377
(219 MiB / 48.5% CPU)
62012 1.01× 0.99× ≥ 0.70×
twp-cli-plus-cache-hit-http1 vs cache cold 22746
(126 MiB / 56.2% CPU)
22787 42493
(229 MiB / 57.5% CPU)
42772 0.99× 0.99× ≥ 0.90×
twp-cli-static-http1 vs CLI 54029
(109 MiB / 50.5% CPU)
54393 113266
(214 MiB / 49.6% CPU)
113624 1.66× 1.83× ≥ 0.85×
twp-cli-logging-http1 vs CLI 32816
(118 MiB / 47.8% CPU)
33026 61585
(208 MiB / 48.3% CPU)
61730 1.01× 0.99× ≥ 0.90×
twp-cli-lb-leasttime-http1 vs route 30541
(134 MiB / 49.5% CPU)
30570 57411
(243 MiB / 50.0% CPU)
58491 0.94× 0.93× ≥ 0.85×
twp-cli-dialect-twp-http1 vs CLI 32828
(114 MiB / 48.9% CPU)
32933 61712
(210 MiB / 48.5% CPU)
61763 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 32128 / TLS 26649; Linux H1 59115 / TLS 47590. Laptop smoke ratios stay on Performance Local Lab — Editions.

Cross-version (7.0 vs 6.0)

Gate 2 prerequisite before the v7.0.0 tag: run compare-cross-version (reverse matrix, routes unset) on develop and compare against committed 6.0 baselines from GHA 33087088466 (tools/RpsLoadProbe/results/baseline-6.0-win.csv / baseline-6.0-linux.csv).

Gate Threshold
Peer-normalized RPS (when YARP peer exists) (TWP÷YARP)_7 ÷ (TWP÷YARP)_6 ≥ 0.90
Absolute RPS floor (TWP arms) 7.0 ÷ 6.0 ≥ 0.70 (runner heat — peers move with the box)
RSS 7.0 ÷ 6.0 ≤ 1.10 per TWP arm @ c=64

nginx/YARP absolute RPS is not gated (same-runner noise). 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-*.csv

Pending clean Gate 2 re-run after peer-normalized validator (prior absolute-only run 33248790383 failed because nginx/YARP/TWP all dropped ~0.78× on a cooler runner — not a product regression).

Heavier reverse workloads

Separate from the tiny-GET matrix. Same measurement environments. Modes: compare-bodies, compare-post, compare-lossy, compare-tls-cost, compare-arch in RpsLoadProbe. Dispatch each independently via workflow_dispatch (no need to re-run full compare-product). PUT with the same body is the same proxy work as POST; DELETE with no body matches GET — only POST is published. Bodies/POST/lossy stay half-duplex. compare-arch is the slow-consumer / early-response / duplex set. Laptop numbers are on Performance Local Lab; CI medians go in the tables below.

Lossy link = userspace shim (not kernel netem): TCP gets per-buffer delay + occasional whole-connection stalls (honest HOL for multiplexed H2); UDP gets per-datagram delay + drops (QUIC). compare-lossy publishes H1/H2/H3; H3 is where the protocol design is supposed to matter.

Windows — heavier reverse GET (64 KiB / 256 KiB)

Median of 3 repeats on windows-latest @ 9d7c2966. Source: Actions 32871900682 (compare-bodies). Warmup 2s / measure 8s. RPS cells include (MiB / CPU%) footprints.

Body Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
64 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 10,297
(99 MiB / 45.5% CPU)
10,564
(99 MiB / 45.5% CPU)
717
(137 MiB / 24.6% CPU)
739
(137 MiB / 24.6% CPU)
9,283
(110 MiB / 44.7% CPU)
9,413
(110 MiB / 44.7% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 9,135
(168 MiB / 46.2% CPU)
9,135
(168 MiB / 46.2% CPU)
626
(137 MiB / 24.8% CPU)
637
(137 MiB / 24.8% CPU)
7,533
(108 MiB / 49.7% CPU)
7,745
(108 MiB / 49.7% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 4,728
(108 MiB / 39.8% CPU)
4,900
(108 MiB / 39.8% CPU)
Not possible (no QUIC) Not possible (no QUIC) 3,995
(177 MiB / 49.0% CPU)
4,035
(177 MiB / 49.0% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 3,056
(88 MiB / 42.6% CPU)
3,271
(88 MiB / 42.6% CPU)
191
(136 MiB / 24.8% CPU)
194
(136 MiB / 24.8% CPU)
2,488
(115 MiB / 45.7% CPU)
2,839
(115 MiB / 45.7% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 2,568
(115 MiB / 38.9% CPU)
2,639
(115 MiB / 38.9% CPU)
163
(137 MiB / 24.9% CPU)
163
(137 MiB / 24.9% CPU)
2,003
(132 MiB / 41.6% CPU)
2,003
(132 MiB / 41.6% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 1,210
(88 MiB / 39.3% CPU)
1,254
(88 MiB / 39.3% CPU)
Not possible (no QUIC) Not possible (no QUIC) 1,100
(125 MiB / 43.9% CPU)
1,136
(125 MiB / 43.9% 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×.

Linux — heavier reverse GET (64 KiB / 256 KiB)

Median of 3 repeats @ 9d7c2966. Source: Actions 32871900682 (compare-bodies). Warmup 2s / measure 8s.

Body Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
64 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 7,902
(170 MiB / 44.4% CPU)
7,902
(170 MiB / 44.4% CPU)
5,518
(96 MiB / 52.5% CPU)
5,518
(96 MiB / 52.5% CPU)
6,420
(158 MiB / 48.8% CPU)
6,420
(158 MiB / 48.8% CPU)
64 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 5,831
(218 MiB / 38.9% CPU)
5,831
(218 MiB / 38.9% CPU)
1,790
(95 MiB / 23.6% CPU)
1,925
(95 MiB / 23.6% CPU)
4,739
(161 MiB / 45.9% CPU)
4,739
(161 MiB / 45.9% CPU)
64 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 5,384
(177 MiB / 44.5% CPU)
5,384
(177 MiB / 44.5% CPU)
1,614
(113 MiB / 22.0% CPU)
1,718
(113 MiB / 22.0% CPU)
4,236
(219 MiB / 51.1% CPU)
4,236
(219 MiB / 51.1% CPU)
256 KiB HTTP/1 · TLS HTTP/1 · plain 🥇 2,742
(121 MiB / 37.2% CPU)
2,742
(121 MiB / 37.2% CPU)
1,732
(95 MiB / 53.3% CPU)
1,732
(95 MiB / 53.3% CPU)
2,142
(171 MiB / 45.9% CPU)
2,142
(171 MiB / 45.9% CPU)
256 KiB HTTP/2 · TLS HTTP/1 · plain 🥇 1,535
(206 MiB / 32.7% CPU)
1,535
(206 MiB / 32.7% CPU)
538
(96 MiB / 18.6% CPU)
538
(96 MiB / 18.6% CPU)
1,319
(164 MiB / 42.7% CPU)
1,330
(164 MiB / 42.7% CPU)
256 KiB HTTP/3 · QUIC HTTP/1 · plain 🥇 1,432
(148 MiB / 43.1% CPU)
1,432
(148 MiB / 43.1% CPU)
448
(107 MiB / 23.3% CPU)
448
(107 MiB / 23.3% CPU)
1,270
(218 MiB / 47.4% CPU)
1,270
(218 MiB / 47.4% 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.

Windows — POST 64 KiB request + 64 KiB response

Median of 3 repeats on windows-latest @ 9d7c2966. Source: Actions 32866714851 (compare-post).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 8,612
(84 MiB / 42.0% CPU)
9,133
(84 MiB / 42.0% CPU)
497
(138 MiB / 24.8% CPU)
538
(138 MiB / 24.8% CPU)
5,837
(96 MiB / 56.0% CPU)
6,283
(96 MiB / 56.0% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 6,241
(196 MiB / 49.7% CPU)
6,242
(196 MiB / 49.7% CPU)
486
(139 MiB / 24.9% CPU)
493
(139 MiB / 24.9% CPU)
5,106
(118 MiB / 50.9% CPU)
5,147
(118 MiB / 50.9% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 2,691
(118 MiB / 43.7% CPU)
2,937
(118 MiB / 43.7% CPU)
Not possible (no QUIC) Not possible (no QUIC) 2,549
(132 MiB / 49.5% CPU)
2,747
(132 MiB / 49.5% CPU)

TWP leads H1 POST (1.5× YARP), H2 POST (1.2× YARP), and H3 POST (~1.1× YARP).

Linux — POST 64 KiB request + 64 KiB response

Median of 3 repeats @ 9d7c2966. Source: Actions 32866714851 (compare-post).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 6,746
(127 MiB / 41.8% CPU)
6,746
(127 MiB / 41.8% CPU)
5,443
(98 MiB / 44.8% CPU)
5,443
(98 MiB / 44.8% CPU)
4,410
(176 MiB / 53.8% CPU)
4,410
(176 MiB / 53.8% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 4,720
(220 MiB / 47.0% CPU)
4,720
(220 MiB / 47.0% CPU)
2,362
(106 MiB / 21.8% CPU)
2,475
(106 MiB / 21.8% CPU)
3,616
(144 MiB / 48.3% CPU)
3,626
(144 MiB / 48.3% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 3,306
(223 MiB / 43.7% CPU)
3,306
(223 MiB / 43.7% CPU)
835
(111 MiB / 24.6% CPU)
835
(111 MiB / 24.6% CPU)
2,959
(260 MiB / 51.5% CPU)
2,959
(260 MiB / 51.5% 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×.

Windows — lossy / high-RTT (H2 HOL / H3 loss)

Userspace 5 ms one-way delay + 1% TCP connection stall (H1/H2) or UDP datagram drop (H3); 64 KiB GET. H1/H2: median of 3 repeats on windows-latest @ 9d7c2966 — 32866717729 (compare-lossy). H3: GHA Windows userspace UDP shim collapses (sustain 0); published H3 row is the laptop quic-http3 remasure under the same delay/loss workload (Performance Local Lab, `windows-20260822-lossy-h3-quic/).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 🥇 663
(98 MiB / 4.8% CPU)
663
(98 MiB / 4.8% CPU)
634
(137 MiB / 19.6% CPU)
634
(137 MiB / 19.6% CPU)
662
(113 MiB / 5.7% CPU)
662
(113 MiB / 5.7% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 59
(122 MiB / 1.7% CPU)
88
(122 MiB / 1.7% CPU)
18
(137 MiB / 0.5% CPU)
18
(137 MiB / 0.5% CPU)
18
(84 MiB / 1.1% CPU)
18
(84 MiB / 1.1% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 1,572 1,572 Not possible (no QUIC) Not possible 0 50

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 remasure kept above).

Linux — lossy / high-RTT (H2 HOL / H3 loss)

Median of 3 repeats @ 9d7c2966. Source: 32866717729 (compare-lossy; lossy H3 uses quic-http3).

Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
HTTP/1 · TLS HTTP/1 · plain 1,201
(142 MiB / 12.9% CPU)
1,201
(142 MiB / 12.9% CPU)
🥇 1,206
(97 MiB / 11.8% CPU)
1,206
(97 MiB / 11.8% CPU)
1,197
(144 MiB / 16.6% CPU)
1,197
(144 MiB / 16.6% CPU)
HTTP/2 · TLS HTTP/1 · plain 🥇 309
(191 MiB / 6.5% CPU)
309
(191 MiB / 6.5% CPU)
40
(95 MiB / 0.3% CPU)
42
(95 MiB / 0.3% CPU)
40
(118 MiB / 1.4% CPU)
44
(118 MiB / 1.4% CPU)
HTTP/3 · QUIC HTTP/1 · plain 🥇 332
(149 MiB / 13.3% CPU)
332
(149 MiB / 13.3% CPU)
88
(105 MiB / 2.7% CPU)
88
(105 MiB / 2.7% CPU)
330
(176 MiB / 17.9% CPU)
330
(176 MiB / 17.9% CPU)

TWP H2 HOL ≫ YARP (~7.7×). H3 TWP÷YARP ≈ 1×.

Architecture-sensitive

compare-arch isolates slow app readers, origin-early response, H2 duplex, and WebSocket echo. See TWP vs YARP IO model. Laptop 1-rep numbers are on Performance Local Lab.

Median of 3 repeats on matched 4 vCPU / 16 GiB runners @ 9d7c2966 (32866720742) (compare-arch). Slow consumer = 256 KiB GET, 16 KiB read + 8 ms sleep. Early response = 64 KiB POST, origin writes after 8 KiB. Duplex H2 = overlapping 64 KiB POST on H2 TLS↔H2 TLS. WebSocket = echo round-trips/sec.

compare-lossy (slow network) is already published above; it is not a slow app reader.

Windows

Scenario Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 🥇 248
(91 MiB / 3.8% CPU)
248
(91 MiB / 3.8% CPU)
209
(140 MiB / 24.7% CPU)
209
(140 MiB / 24.7% CPU)
248
(110 MiB / 4.9% CPU)
248
(110 MiB / 4.9% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 248
(125 MiB / 3.8% CPU)
248
(125 MiB / 3.8% CPU)
166
(137 MiB / 24.6% CPU)
166
(137 MiB / 24.6% CPU)
🥇 249
(112 MiB / 7.4% CPU)
249
(112 MiB / 7.4% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 264
(99 MiB / 17.1% CPU)
264
(99 MiB / 17.1% CPU)
Not possible (no QUIC) Not possible (no QUIC) 264
(160 MiB / 21.3% CPU)
264
(160 MiB / 21.3% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 🥇 6,507
(84 MiB / 43.0% CPU)
6,881
(84 MiB / 43.0% CPU)
347
(137 MiB / 24.9% CPU)
419
(137 MiB / 24.9% CPU)
3,256
(117 MiB / 39.5% CPU)
3,606
(117 MiB / 39.5% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 5,011
(153 MiB / 48.6% CPU)
5,011
(153 MiB / 48.6% CPU)
0
(139 MiB / 24.8% CPU)
383
(139 MiB / 24.8% CPU)
2,967
(97 MiB / 39.6% CPU)
3,184
(97 MiB / 39.6% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 2,153
(125 MiB / 42.8% CPU)
2,256
(125 MiB / 42.8% CPU)
Not possible (no QUIC) Not possible (no QUIC) 1,874
(154 MiB / 53.3% CPU)
2,034
(154 MiB / 53.3% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 🥇 784
(124 MiB / 13.2% CPU)
1,270
(124 MiB / 13.2% CPU)
Not possible Not possible 18
(120 MiB / 30.7% CPU)
2,135
(120 MiB / 30.7% CPU)
Duplex (WebSocket / extended CONNECT) HTTP/1 · TLS HTTP/1 · plain 🥇 32,785
(97 MiB / 40.8% CPU)
32,785
(97 MiB / 40.8% CPU)
19,252
(138 MiB / 24.9% CPU)
19,803
(138 MiB / 24.9% CPU)
30,820
(85 MiB / 43.6% CPU)
30,820
(85 MiB / 43.6% CPU)

Linux

Scenario Client Origin TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Slow consumer (256 KiB GET, throttled client read) HTTP/1 · TLS HTTP/1 · plain 🥇 468
(115 MiB / 9.5% CPU)
468
(115 MiB / 9.5% CPU)
416
(96 MiB / 9.6% CPU)
416
(96 MiB / 9.6% CPU)
418
(138 MiB / 13.6% CPU)
418
(138 MiB / 13.6% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/2 · TLS HTTP/1 · plain 473
(131 MiB / 17.8% CPU)
473
(131 MiB / 17.8% CPU)
462
(96 MiB / 17.3% CPU)
462
(96 MiB / 17.3% CPU)
🥇 474
(146 MiB / 23.5% CPU)
474
(146 MiB / 23.5% CPU)
Slow consumer (256 KiB GET, throttled client read) HTTP/3 · QUIC HTTP/1 · plain 🥇 474
(125 MiB / 34.3% CPU)
474
(125 MiB / 34.3% CPU)
119
(100 MiB / 11.4% CPU)
238
(100 MiB / 11.4% CPU)
472
(193 MiB / 40.0% CPU)
472
(193 MiB / 40.0% CPU)
Early response (origin writes after first request chunk) HTTP/1 · TLS HTTP/1 · plain 🥇 5,039
(149 MiB / 45.3% CPU)
5,039
(149 MiB / 45.3% CPU)
3,904
(98 MiB / 49.4% CPU)
3,904
(98 MiB / 49.4% CPU)
3,425
(175 MiB / 55.3% CPU)
3,425
(175 MiB / 55.3% CPU)
Early response (origin writes after first request chunk) HTTP/2 · TLS HTTP/1 · plain 🥇 3,661
(233 MiB / 44.1% CPU)
3,661
(233 MiB / 44.1% CPU)
0
(103 MiB / 24.6% CPU)
1,803
(103 MiB / 24.6% CPU)
2,370
(148 MiB / 47.6% CPU)
2,570
(148 MiB / 47.6% CPU)
Early response (origin writes after first request chunk) HTTP/3 · QUIC HTTP/1 · plain 🥇 3,018
(193 MiB / 43.5% CPU)
3,018
(193 MiB / 43.5% CPU)
0
(114 MiB / 24.7% CPU)
545
(114 MiB / 24.7% CPU)
2,232
(262 MiB / 46.7% CPU)
2,232
(262 MiB / 46.7% CPU)
Duplex (both directions live) HTTP/2 · TLS HTTP/2 · TLS 🥇 185
(124 MiB / 9.0% CPU)
282
(124 MiB / 9.0% CPU)
Not possible Not possible 138
(139 MiB / 44.6% CPU)
1,882
(139 MiB / 44.6% CPU)
Duplex (WebSocket / extended CONNECT) HTTP/1 · TLS HTTP/1 · plain 35,088
(121 MiB / 44.5% CPU)
35,088
(121 MiB / 44.5% CPU)
🥇 39,303
(97 MiB / 37.1% CPU)
39,303
(97 MiB / 37.1% CPU)
32,013
(122 MiB / 44.9% CPU)
32,013
(122 MiB / 44.9% 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.

TLS termination cost (H1 TLS → cleartext origin)

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.

Windows

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

Workload TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Keep-alive · tiny GET 🥇 20,709
(82 MiB / 48.3% CPU)
20,709
(82 MiB / 48.3% CPU)
9,096
(137 MiB / 24.8% CPU)
9,158
(137 MiB / 24.8% CPU)
17,934
(103 MiB / 48.1% CPU)
17,934
(103 MiB / 48.1% CPU)
New-connection · tiny GET 🥇 739
(65 MiB / 10.4% CPU)
745
(65 MiB / 10.4% CPU)
252
(136 MiB / 24.2% CPU)
256
(136 MiB / 24.2% CPU)
591
(93 MiB / 10.1% CPU)
607
(93 MiB / 10.1% CPU)
Keep-alive · 256 KiB GET 🥇 2,698
(87 MiB / 46.2% CPU)
2,971
(87 MiB / 46.2% CPU)
172
(137 MiB / 24.7% CPU)
174
(137 MiB / 24.7% CPU)
2,569
(134 MiB / 44.0% CPU)
2,569
(134 MiB / 44.0% CPU)

Linux

Median of 3 repeats @ 9d7c2966. Source: Actions 32866723562 (compare-tls-cost).

Workload TWP sustain TWP peak nginx sustain nginx peak YARP sustain YARP peak
Keep-alive · tiny GET 23,687
(104 MiB / 49.1% CPU)
23,687
(104 MiB / 49.1% CPU)
🥇 25,897
(95 MiB / 41.3% CPU)
26,177
(95 MiB / 41.3% CPU)
20,716
(133 MiB / 50.5% CPU)
20,716
(133 MiB / 50.5% CPU)
New-connection · tiny GET 962
(124 MiB / 47.5% CPU)
973
(124 MiB / 47.5% CPU)
🥇 1,005
(96 MiB / 44.2% CPU)
1,008
(96 MiB / 44.2% CPU)
941
(148 MiB / 45.8% CPU)
941
(148 MiB / 45.8% CPU)
Keep-alive · 256 KiB GET 🥇 2,690
(126 MiB / 36.5% CPU)
2,690
(126 MiB / 36.5% CPU)
1,749
(96 MiB / 53.6% CPU)
1,749
(96 MiB / 53.6% CPU)
2,146
(172 MiB / 45.9% CPU)
2,146
(172 MiB / 45.9% 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.

Other measurements

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

Raising limits on large hosts

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;
};

Clone this wiki locally