3 Separate optimizations towards resolution of #8583:
1. (probably most substantial:) ``PrefixTree.replaceAll` did O(n^2)
allocations, causing substantial GC time:
```ts
const lineBit = line.substring(idxInOld);
const [oldValue, newValue] = this.findLongestMatch(lineBit);
```
For every character pos in a line this allocated a substring for the
entire remainder. Over one line that's O(n^2) bytes allocated, and
`processPassOutput` runs this on every line of every before/after IR
dump — which for an opt-pipeline run is enormous. This is a major
contributor to both the `replaceAll`/`processPassOutput` self-time and
the 20% GC.
Fix: make `findLongestMatch` accept a start offset and walk the existing
line string in place.
2. `JSON.stringify` large payloads once instead of twice, when caching
to S3
3. `processPassOutput` called `PrefixTree.replaceAll` which did a costly
build of replacement map but didn't use them. Now calls the new
`PrefixTree.replaceAllText`.
-------
The time measurements on the live site and on my machine differ
substantially. Before pursuing more aggressive optimizations I want to
check the impact of these, see if more work is needed.
One additional direction for (major) optimization: I think today each
pass dump is processed twice, once as 'before' and 2nd time as 'after'
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>