diff --git a/tools/RpsLoadProbe/apply-wiki-paste.ps1 b/tools/RpsLoadProbe/apply-wiki-paste.ps1
index a21ef2fd9..d599a7772 100644
--- a/tools/RpsLoadProbe/apply-wiki-paste.ps1
+++ b/tools/RpsLoadProbe/apply-wiki-paste.ps1
@@ -15,23 +15,28 @@ $ge = [char]0x2265
$rarr = [char]0x2192
$endash = [char]0x2013
-function Fix-Utf8Mojibake([string]$text) {
- if ([string]::IsNullOrEmpty($text)) { return $text }
- try {
- $bytes = [System.Text.Encoding]::GetEncoding(28591).GetBytes($text)
- return [System.Text.Encoding]::UTF8.GetString($bytes)
- }
- catch {
- return $text
- }
+function Test-Utf8Mojibake([string]$text) {
+ return $text -match ([string][char]0x00C2 + [char]0x00B7) -or
+ $text -match ([string][char]0x00C3 + [char]0x2014) -or
+ $text -match ([string][char]0x00C3 + [char]0x00B7) -or
+ $text -match ([string][char]0x00F0 + [char]0x0178 + [char]0x00A5 + [char]0x2021)
}
-$raw = Fix-Utf8Mojibake ([System.IO.File]::ReadAllText((Resolve-Path $PasteFile), [System.Text.Encoding]::UTF8))
+$raw = [System.IO.File]::ReadAllText((Resolve-Path $PasteFile), [System.Text.Encoding]::UTF8)
+if (Test-Utf8Mojibake $raw) {
+ & "$PSScriptRoot/repair-wiki-mojibake.ps1" -WikiFile $PasteFile
+ $raw = [System.IO.File]::ReadAllText((Resolve-Path $PasteFile), [System.Text.Encoding]::UTF8)
+}
function Get-Section([string]$name) {
- $pattern = "(?s)---$name---\r?\n(.*?)(?=---|\z)"
- if ($raw -notmatch $pattern) { throw "Missing section $name" }
- return $Matches[1].TrimEnd()
+ $marker = "---$name---"
+ $start = $raw.IndexOf($marker)
+ if ($start -lt 0) { throw "Missing section $name" }
+ $start += $marker.Length
+ while ($start -lt $raw.Length -and ($raw[$start] -eq "`r" -or $raw[$start] -eq "`n")) { $start++ }
+ $next = [regex]::Match($raw.Substring($start), '\r?\n---[A-Z_]+---')
+ $end = if ($next.Success) { $start + $next.Index } else { $raw.Length }
+ return $raw.Substring($start, $end - $start).TrimEnd()
}
$winRev = Get-Section 'WIN_REVERSE'
@@ -43,7 +48,7 @@ $wiki = [System.IO.File]::ReadAllText((Resolve-Path $WikiFile), [System.Text.Enc
$runUrl = "https://github.com/justcoding121/titanium-web-proxy/actions/runs/$PrimaryRunId"
$productLine = "- Product refresh: ``compare-product`` @ ``$HeadSha`` $em [$PrimaryRunId]($runUrl). Heavier/saturation/tls:"
-$wiki = [regex]::Replace($wiki, '- Product refresh:.*', $productLine, 1)
+$wiki = [regex]::Replace($wiki, '- Product refresh:.*', [System.Text.RegularExpressions.MatchEvaluator]{ $productLine }, 1)
$winRevHeader = "Median of **3 repeats** on ``windows-latest`` (4 vCPU / 16 GiB). Bare reverse 5${mul}5 @ ``$HeadSha`` $em ``compare-product`` [$PrimaryRunId]($runUrl). Warmup 2s / measure 8s; concurrency 8, 16, 32, 64. Prefer TWP${div}peer ratios over absolute RPS. **RPS cells** include median RSS / CPU at the peak-RPS step as ``
(MiB / CPU%)``. nginx terminate peers use ``keepalive 256`` + streaming buffers. Laptop High-perf / cool-paired numbers stay on the [local lab](Performance-Local-Lab)."
@@ -60,24 +65,32 @@ $linHdr = "## Linux $em Titanium vs nginx vs YARP"
$wiki = [regex]::Replace($wiki,
"(?s)($([regex]::Escape($winHdr))\r?\n\r?\n### Reverse\r?\n\r?\n).*?(?=\r?\n### MITM)",
- "`${1}$winRevHeader`n`n$winRev`n",
+ [System.Text.RegularExpressions.MatchEvaluator]{
+ param($m) $m.Groups[1].Value + $winRevHeader + "`n`n" + $winRev + "`n"
+ },
1)
$wiki = [regex]::Replace($wiki,
'(?s)(### MITM \(TWP only\)\r?\n\r?\n).*?(?=\r?\n## Linux)',
- "`${1}$mitmNote`n`n$winMitm`n",
+ [System.Text.RegularExpressions.MatchEvaluator]{
+ param($m) $m.Groups[1].Value + $mitmNote + "`n`n" + $winMitm + "`n"
+ },
1)
$wiki = [regex]::Replace($wiki,
"(?s)($([regex]::Escape($linHdr))\r?\n\r?\n### Reverse\r?\n\r?\n).*?(?=\r?\n### MITM)",
- "`${1}$linRevHeader`n`n$linRev`n",
+ [System.Text.RegularExpressions.MatchEvaluator]{
+ param($m) $m.Groups[1].Value + $linRevHeader + "`n`n" + $linRev + "`n"
+ },
1)
$idx = $wiki.IndexOf($linHdr)
$tail = $wiki.Substring($idx)
$tail = [regex]::Replace($tail,
'(?s)(### MITM \(TWP only\)\r?\n\r?\n).*?(?=\r?\n## Heavier)',
- "`${1}$mitmNote`n`n$linMitm`n",
+ [System.Text.RegularExpressions.MatchEvaluator]{
+ param($m) $m.Groups[1].Value + $mitmNote + "`n`n" + $linMitm + "`n"
+ },
1)
$wiki = $wiki.Substring(0, $idx) + $tail
diff --git a/tools/RpsLoadProbe/fix-wiki-encoding.ps1 b/tools/RpsLoadProbe/fix-wiki-encoding.ps1
new file mode 100644
index 000000000..ab7da38a1
--- /dev/null
+++ b/tools/RpsLoadProbe/fix-wiki-encoding.ps1
@@ -0,0 +1,22 @@
+# Repair paste output or wiki/Performance.md, then optionally apply paste to the wiki.
+param(
+ [string] $PasteFile = 'tools/RpsLoadProbe/results/wiki-paste-out2.txt',
+ [string] $WikiFile = 'wiki/Performance.md',
+ [switch] $ApplyPaste,
+ [string] $HeadSha = 'df172718',
+ [string] $PrimaryRunId = '33041445371'
+)
+
+$ErrorActionPreference = 'Stop'
+
+if ($ApplyPaste) {
+ & "$PSScriptRoot/apply-wiki-paste.ps1" -PasteFile $PasteFile -WikiFile $WikiFile -HeadSha $HeadSha -PrimaryRunId $PrimaryRunId
+ & "$PSScriptRoot/repair-wiki-mojibake.ps1" -WikiFile $WikiFile
+ exit 0
+}
+
+if (Test-Path $PasteFile) {
+ & "$PSScriptRoot/repair-wiki-mojibake.ps1" -WikiFile $PasteFile
+}
+
+Write-Host "To regenerate paste with UTF-8: pwsh -NoProfile -Command `"& { `$o = & '$PSScriptRoot/paste-compare-product-wiki.ps1' -RunIds '33041445371,33055267086,33055272140'; [IO.File]::WriteAllLines('$PasteFile', @(`$o), (New-Object Text.UTF8Encoding `$false)) }`""
diff --git a/tools/RpsLoadProbe/repair-wiki-mojibake.ps1 b/tools/RpsLoadProbe/repair-wiki-mojibake.ps1
new file mode 100644
index 000000000..983fe155b
--- /dev/null
+++ b/tools/RpsLoadProbe/repair-wiki-mojibake.ps1
@@ -0,0 +1,40 @@
+# Repair common UTF-8 mojibake sequences in wiki/Performance.md without touching valid Unicode.
+param(
+ [string] $WikiFile = 'wiki/Performance.md'
+)
+
+$ErrorActionPreference = 'Stop'
+
+$middleDot = [char]0x00B7
+$middleDotM = [string][char]0x00C2 + [char]0x00B7
+$times = [char]0x00D7
+$timesM = [string][char]0x00C3 + [char]0x2014
+$div = [char]0x00F7
+$divM = [string][char]0x00C3 + [char]0x00B7
+$medal = [char]::ConvertFromUtf32(0x1F947)
+$medalM = [string][char]0x00F0 + [char]0x0178 + [char]0x00A5 + [char]0x2021
+
+$wikiPath = Resolve-Path $WikiFile
+$wiki = [System.IO.File]::ReadAllText($wikiPath, [System.Text.Encoding]::UTF8)
+
+function Count-Mojibake([string]$text) {
+ return @{
+ Dot = ([regex]::Matches($text, [regex]::Escape($middleDotM))).Count
+ Times = ([regex]::Matches($text, [regex]::Escape($timesM))).Count
+ Div = ([regex]::Matches($text, [regex]::Escape($divM))).Count
+ Medal = ([regex]::Matches($text, [regex]::Escape($medalM))).Count
+ Bad = ([regex]::Matches($text, [char]0xFFFD)).Count
+ }
+}
+
+$before = Count-Mojibake $wiki
+$wiki = $wiki.Replace($middleDotM, $middleDot)
+$wiki = $wiki.Replace($timesM, $times)
+$wiki = $wiki.Replace($divM, $div)
+$wiki = $wiki.Replace($medalM, $medal)
+$after = Count-Mojibake $wiki
+
+$utf8 = New-Object System.Text.UTF8Encoding $false
+[System.IO.File]::WriteAllText($wikiPath, $wiki, $utf8)
+Write-Host ('Repaired {0}: dot {1}->{2} times {3}->{4} div {5}->{6} medal {7}->{8} bad {9}' -f `
+ $WikiFile, $before.Dot, $after.Dot, $before.Times, $after.Times, $before.Div, $after.Div, $before.Medal, $after.Medal, $after.Bad)
diff --git a/wiki/Performance-Profiling.md b/wiki/Performance-Profiling.md
index 0a767bf56..cc610bc90 100644
--- a/wiki/Performance-Profiling.md
+++ b/wiki/Performance-Profiling.md
@@ -328,7 +328,7 @@ The [architecture-sensitive](Performance-Local-Lab#architecture-sensitive) lapto
| H3→H1 latency bundle (skip drain / skip Flush / HEADERS+DATA coalesce) | Cool absolute win (`cool-h3h1-latency-bundle-20260823/`) + CI bridges [32652931261](https://github.com/justcoding121/titanium-web-proxy/actions/runs/32652931261) @ `3f948409` | Cool c=64 TWP ~25k (tip ~20–23k); laptop YARP ~30k → cool TY ~0.85×. Trace @ c=64: p50 gap not exclusive CPU. | **CI miss**: Win H3→H1 **0.92× → ~0.87×** (13,391 / 15,444). Lin H3→H1 still leads (~1.11×). Lossy Lin H3 improved ~0.76×→~**0.89×** (278/314) but still <1.00×. **Reverted** (`2bf18d75`). |
| H2→H1 Memory ~5–9× YARP at RPS parity | Saturation RSS sampler + bag lifetime analysis | `ConcurrentBag` PendingSynthetics/Finalizations retained completed Tasks (session closures) for client H2 conn life; full SessionEventArgs per stream on IsFastPath | **`Http2PendingWork`** remove-on-complete; H2→H1 warm `TryRentPooled` + `HeaderBuilder` wire (H3→H1 analogue). Remeasure Block B Memory. Do not shrink windows / single-conn. |
| H3→H1 Win ~0.993× residual after one-pass QPACK | Cool A/B + gen0 on post-status path | Per-response `MemoryStream` QPACK builder + Latin-1 string round-trip + `new byte[]` tiny body | ThreadStatic **`ResponseBlockBuilder` rent**; span `AddHeader`; BufferPool body when `Available` covers CL. Bridges CI @ `0ff3673c` / [32685354747](https://github.com/justcoding121/titanium-web-proxy/actions/runs/32685354747): Win **1.12×** (27,046 / 24,085); Linux **1.09×**. |
-| H3→H3 Win CI ~0.85× YARP; MITM Full÷Reverse ≪0.80 on H2/H3/H1 arms ([32960766249](https://github.com/justcoding121/titanium-web-proxy/actions/runs/32960766249)) | Cool paired A/B (`h3h3-dig/`) + `compare-product` MITM arms | H3→H3: per-frame QUIC I/O. MITM Full: probe-only fast path coupled product code to RPS harness header name; H2 `!wouldInjectVia` bail; H3 Via gate blocked preencoded relay with default pseudonym | **Landed v1:** `MitmCompressedRelayHelper` (`HeaderRelayBaseline` append-only diff, max 4 unique headers); H2/H3 append literals on static HPACK/QPACK blocks + Via as wire literal; H1 terminate-lite gate. Probe name stays in RpsLoadProbe harness only. Win spot Full÷Reverse @ c=64: H2 plain **0.858×**, H2 TLS **0.990×**, H3→H1 TLS **1.091×**, H3→H3 **0.999×**. GHA `compare-product` ×3 on `mitm/relay-publish` pending for wiki tables |
+| H3→H3 Win CI ~0.85× YARP; MITM Full÷Reverse ≪0.80 on H2/H3/H1 arms ([32960766249](https://github.com/justcoding121/titanium-web-proxy/actions/runs/32960766249)) | Cool paired A/B (`h3h3-dig/`) + `compare-product` MITM arms | H3→H3: per-frame QUIC I/O. MITM Full: probe-only fast path coupled product code to RPS harness header name; H2 `!wouldInjectVia` bail; H3 Via gate blocked preencoded relay with default pseudonym | **Landed v1** @ `df172718`: `MitmCompressedRelayHelper` append-only (max 4 unique headers); H2 zero-copy append suffix; all MITM arms ≥ **0.70×** on GHA median ([33041445371](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33041445371) ×3). **Landed v2:** `MitmStaticRebuildHelper` drop-only static rebuild (max 4 unique drops) + non-unique trailing append (second `Set-Cookie`); modify value / body still full re-encode. Three MITM tiers: **Lite** (unchanged relay), **Append/Strip-lite** (append or drop-only on static GET), **Full session** (modify/body/multi-edit). GHA remeasure pending on `mitm/relay-v2-publish`. |
| H3→H1 sticky ConcurrentBag TCP pool (bypass factory poolLock) | Cool tip-vs-sticky both orders @ c=32 (`cool-sticky-vs-tip-20260823/`) | Hypothesis: CI 4 vCPU multiplex tax on poolLock/queue | **Cool flat** (~1.07× both). Reverted. |
| H3→H1 Request/Response ConcurrentBag + QPACK ArrayPool EncodeResponse | Cool same-thermal tip-vs-pool @ c=32 (`cool-pool-qpack-20260823/`) | Hypothesis: HeaderCollection graph + QPACK `byte[]` gen0 under 32 wakeups | **Cool flat** (tip ~1.08×, pool ~1.06×). TWP absolute sometimes +high single digits; ratio not ≥+2%. Reverted. |
| H3→H1 FIN-drain overlap with origin TCP + message pool | Cool both orders (`cool-pool-overlap-20260823/`) | Hypothesis: hide Quic FIN read behind origin FillBuffer (do not skip drain) | **Cool miss** (order-noisy mean ≪ tip). Reverted. |
diff --git a/wiki/Performance.md b/wiki/Performance.md
index c62977090..e6be48836 100644
--- a/wiki/Performance.md
+++ b/wiki/Performance.md
@@ -147,7 +147,7 @@ Same layout as Block B. Requires QuicListener; nginx only with `http_v3_module`
- **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` @ `df172718` — [33041445371](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33041445371). Heavier/saturation/tls:
+- Product refresh: `compare-product` @ `acfb27e1` — [33087088466](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087088466). Heavier/saturation/tls:
```powershell
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-product
@@ -199,30 +199,109 @@ Median of **3 repeats** on `windows-latest` (4 vCPU / 16 GiB). Bare reverse 5×5
### MITM (TWP only)
-Same Client×Origin wires with interception on (`compare-product` [33041445371](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33041445371)). **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).
+Same Client×Origin wires with interception on (`compare-product` [33087088466](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33041445371), [33055267086](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33055267086), [33055272140](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087088466), [33087091622](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087091622), [33105885748](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33105885748) Linux; [33087085235](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087085235), [33087088466](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087088466), [33087091622](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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 @ `df172718` — `compare-product` [33041445371](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33041445371). 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.
+Median of **3 repeats** on `ubuntu-latest` (4 vCPU / 16 GiB). Bare reverse 5×5 @ `acfb27e1` — `compare-product` [33087088466](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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) | *Not possible* | *Not possible* | **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) | *Not possible* | *Not possible* | **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` [33041445371](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33041445371)). **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).
+Same Client×Origin wires with interception on (`compare-product` [33087088466](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33041445371), [33055267086](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33055267086), [33055272140](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087088466), [33087091622](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087091622), [33105885748](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33105885748) Linux; [33087085235](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087085235), [33087088466](https://github.com/justcoding121/titanium-web-proxy/actions/runs/33087088466), [33087091622](https://github.com/justcoding121/titanium-web-proxy/actions/runs/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×** |
## Heavier reverse workloads