Skip to content

Perf hunt round 19: remaining per-char Substring loops, Cavena cp1252 string cache - #14595

Merged
niksedk merged 1 commit into
mainfrom
claude/perf-hunt-round19
Sep 6, 2026
Merged

Perf hunt round 19: remaining per-char Substring loops, Cavena cp1252 string cache#14595
niksedk merged 1 commit into
mainfrom
claude/perf-hunt-round19

Conversation

@niksedk

@niksedk niksedk commented Sep 6, 2026

Copy link
Copy Markdown
Member

Follow-up to #14594 with the candidates that round found but left. Nine verified fixes (BenchmarkDotNet default job, in-process, Apple M4, .NET 10) against a detached baseline worktree of main; the untouched SubRipControl benchmark is flat with identical allocations. Every touched function was dumped over an edge-case corpus on both sides, including the Cavena 890 and Ultech 130 output bytes and Cavena files patched with colour-marker and raw-remap bytes, and the dumps are byte-identical.

Benchmark Before After Speed-up Allocated
DVD Studio Pro load (400 cues) 1,049 us 311 us 3.4x 12.2 MB -> 736 KB
Cavena 890 load (400) 3,895 us 1,206 us 3.2x 16.2 MB -> 1.0 MB
Unknown 81 save (400) 736 us 228 us 3.2x 8.9 MB -> 786 KB
TextST dialog segments (400) 484 us 163 us 3.0x 4.7 MB -> 814 KB
Ultech 130 save (400) 533 us 337 us 1.58x 3.5 MB -> 184 KB
ESub-XF save (400) 8,653 us 6,660 us 1.30x 17.8 MB -> 1.9 MB
RTL start/end reverse (400) 94.9 us 84.0 us 1.13x 639 KB -> 521 KB
FixMissingPeriodsAtEndOfLine 294 us 263 us 1.12x 612 KB -> 496 KB
Cavena 890 save (400) 6,808 us 6,638 us 1.03x 3.8 MB -> 2.5 MB

What was wrong

  • Per-character Substring probes: ESubXf.GenerateLineWithSpan (eight probes per character), TextST.DialogPresentationSegment (copied the rest of the line and then a one-char string per character), Ultech130.Save, DvdStudioPro.DecodeStyles, UnknownSubtitle81.EncodeText and Utilities.ReverseStartAndEndingForRightToLeft (three tail copies per character at the front, a prefix copy per character at the back). All use the span-based StartsWithAt, plus a new EndsWithAt.
  • Cavena 890 reader: FixColors compared a one-char string per character against eight fresh encoding.GetString(new byte[] { .. }) decodes; FixText ran about 150 such decodes per paragraph for constant cp1252 strings. The markers are decoded once per call and the one- and two-byte strings come from a static cache.
  • Cavena 890 writer: text.Substring(i, 3) == "<i>" per character became a span compare; the existing length guards are unchanged.
  • FixMissingPeriodsAtEndOfLine split the whole next paragraph into words to read the first one.

Dropped during the round: caching the new SubRip() in ToLowercaseButKeepTags measured no difference at all (identical allocations), so it is not included.

Verification

  • tests/benchmarks/PerfHuntRound19Benchmarks.cs
  • Byte-identical dump diff (58 cues per format, five Cavena language ids, patched colour/remap bytes)
  • tests/libse: 1916 passed

🤖 Generated with Claude Code

…s, cache Cavena's cp1252 strings

The leftovers from round 18: ESub-XF, TextST, Ultech 130, DVD Studio Pro, Unknown 81 and
the right-to-left start/end reverser all probed tags with line.Substring(i).StartsWith(tag)
for every character (TextST also made a one-char string per character). They now use the
span-based StartsWithAt / EndsWithAt helpers.

Cavena 890: FixColors decoded eight colour markers through encoding.GetString(new byte[])
and allocated a one-char string for every character; FixText ran about 150
encoding.GetString(new byte[] { .. }) calls per paragraph for constant one- and two-byte
cp1252 strings. The markers are decoded once per call and the strings come from a static
cache. The writer's Substring(i, 3) == "<i>" probes are span compares.

FixMissingPeriodsAtEndOfLine split the whole next paragraph into words to read the first
one.

Benchmarks in tests/benchmarks/PerfHuntRound19Benchmarks.cs; outputs (including the Cavena
and Ultech bytes) are byte-identical to the baseline over an edge-case corpus.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@niksedk
niksedk merged commit 6861b18 into main Sep 6, 2026
1 check passed
@niksedk
niksedk deleted the claude/perf-hunt-round19 branch September 6, 2026 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant