Third round of libse micro-optimizations (BenchmarkDotNet-verified)#12541
Merged
Conversation
- Subtitle formats: hoist 15 method-local Regex constructions to static compiled fields (WebVTT, InqScribe, DvSubtitle, TimedText10, TimedTextNoNs, Son, DCinemaSmpte2010, AdvancedSubStationAlpha). Worst offender was WebVTT.RemoveNativeFormatting, which built three COMPILED regexes on every call - compiled construction emits IL and cost ~1.7 ms per conversion before a single cue was processed (benchmarked ~8000x faster with statics). InqScribe built one per text line. - HtmlUtil.RemoveOpenCloseTags: walk matches and compare the tag-name group in place instead of Regex.Replace with a MatchEvaluator closure - the old shape allocated a closure over the tags array, a string per match for the group and one per kept match, and rebuilt the string even when nothing matched. Now returns the original instance when no tag is removed. Runs per paragraph in a dozen format writers. 1.5x faster on a mixed workload. - StringExtensions.ToRtfPart: append the "\u<code>?" escape in parts instead of string concatenation, which allocated two strings per non-ASCII char - noticeable on CJK/Cyrillic subtitles. 1.6x faster, half the allocations. Verified: BenchmarkDotNet on all three; A/B sweeps proving byte-identical output vs the old implementations (30k randomized tag-soup inputs across five tag sets incl. spaced/unclosed/case variants, 30k random RTF inputs incl. RTF-special chars and non-ASCII), plus a reference-equality check for the new no-change fast path. All 485 libse + 233 seconv tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Same method as #12530/#12533. Three optimizations, each with A/B output-identity proof and BenchmarkDotNet numbers (.NET 10, Apple M4).
1. Hoist 15 method-local
new Regex(...)constructions in subtitle formats — up to ~8,000×Eight format files constructed regexes inside methods. The worst was
WebVTT.RemoveNativeFormatting: threeRegexOptions.Compiledregexes built on every call — compiled construction emits IL, costing ~1.7 ms before processing a single cue, on one of the most-converted formats there is. InqScribe built one regex per text line; DvSubtitle six per call; TimedText10/TimedTextNoNs, Son, DCinemaSmpte2010 and the ASSA drawing-tag helper had one or two each. All are nowprivate static readonly ... RegexOptions.Compiled, matching the codebase convention.2.
HtmlUtil.RemoveOpenCloseTags— 1.5× faster, no-op lines allocation-freeUsed per paragraph by a dozen format writers (PAC, Cavena890, TS, BelleNuit, several Unknown formats, translate formatting). The
Regex.Replace+MatchEvaluatorshape allocated a closure overtags, a string per match for the tag-name group, one more per kept match — and rebuilt the string even when nothing matched. Now walks matches, compares the group via span (OrdinalIgnoreCase), and returns the original instance when no tag is removed.(The alloc delta looks modest because the mixed benchmark is dominated by removal-heavy lines; untouched lines drop from full-rebuild to zero.)
3.
StringExtensions.ToRtfPart— 1.6× faster, −52% allocationssb.Append("\\u" + Convert.ToUInt32(c) + "?")allocated two strings per non-ASCII character — painful on CJK/Cyrillic text in the RTF format/copy paths. Now appends the parts separately.Correctness
RemoveOpenCloseTags: 30,000 randomized tag-soup inputs (spaced tags< / i >, unclosed<unclosed, case variants,5 < 6non-tags,<v Speaker Name>) across five tag sets — byte-identical vs the old implementation, plus a reference-equality check on the new no-change fast path.ToRtfPart: 30,000 random inputs mixing RTF-special chars (\,{,}), CRLF, and CJK/Cyrillic/Greek — byte-identical.🤖 Generated with Claude Code