Skip to content

Speed up StartsAndEndsWithTag with allocation-free span checks - #13381

Merged
niksedk merged 3 commits into
SubtitleEdit:mainfrom
ivandrofly:perf-starts-and-ends-with-tag
Aug 9, 2026
Merged

Speed up StartsAndEndsWithTag with allocation-free span checks#13381
niksedk merged 3 commits into
SubtitleEdit:mainfrom
ivandrofly:perf-starts-and-ends-with-tag

Conversation

@ivandrofly

@ivandrofly ivandrofly commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Rewrite Utilities.StartsAndEndsWithTag to skip leading dialog dashes/dots/spaces and trailing punctuation with two small character loops, then match the tags with ReadOnlySpan<char> comparisons - removes the per-call prefix/suffix string concatenations ("- " + startTag, endTag + ".", ...) and the while (text.Contains(" ")) double-space collapse loop (no longer needed, since the character skip handles repeated spaces directly)
  • Roughly 3-4x faster and allocation-free on typical subtitle lines (benchmark below)
  • The character-skip approach accepts a few variants the old hard-coded prefix list rejected (e.g. leading ... without a dialog dash, runs of trailing punctuation like ?!) - this widens the italic-merge behavior in FixInvalidItalicTags slightly, and is now pinned deliberately by tests
  • Tests added: UtilitiesStartsAndEndsWithTagTest covers the old accepted forms and the newly accepted variants; two HtmlUtilTest integration tests pin the resulting italic merges

Benchmark

BenchmarkDotNet v0.14.0, Windows 11, i7-13700, .NET 10.0.10 (X64 RyuJIT AVX2). Old = previous implementation, New = this PR. Tags: <i> / </i>.

Case Input Old New Ratio Old alloc New alloc
BareTags <i>Hello there, how are you?</i> 63.9 ns 20.5 ns 0.32 312 B 0 B
DashPrefix - <i>Hello there, how are you?</i> 70.1 ns 26.6 ns 0.38 312 B 0 B
MidText He said <i>hello</i> to me. 95.1 ns 24.3 ns 0.26 312 B 0 B
NoTags Hello there, how are you today? 4.5 ns 3.2 ns 0.76 0 B 0 B

Test plan

  • dotnet test tests/libse/LibSETests.csproj - 977 passed (includes the new StartsAndEndsWithTag unit tests and FixInvalidItalicTags integration tests)

🤖 Generated with Claude Code

Replace the per-call prefix/suffix string concatenations ("- " + startTag,
endTag + ".", etc.) with two small loops that skip leading dash/dot/space
and trailing punctuation, then compare the tags via ReadOnlySpan without
allocating. Also collapse double spaces with FixExtraSpaces instead of a
repeated string.Replace loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes Utilities.StartsAndEndsWithTag in libse by replacing allocation-heavy string concatenations and repeated Replace loops with allocation-free ReadOnlySpan<char> prefix/suffix checks and simple character-skipping loops. This should reduce GC pressure and improve throughput for common subtitle-tag patterns (e.g., dialog dashes and trailing punctuation around HTML tags).

Changes:

  • Reworked tag detection to skip leading dialog markers (' ', '.', '-') before checking startTag via spans.
  • Reworked end detection to trim common trailing punctuation/whitespace ('.', '!', '?', '-', ' ') before checking endTag via spans.
  • Removed the previous per-call construction of multiple prefix/suffix strings and the repeated double-space replacement loop.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

niksedk and others added 2 commits August 9, 2026 15:10
- Add doc comment stating the skip characters must not overlap the tags
- Guard the trailing-punctuation scan with startIndex instead of 0
- Add tests covering both the old accepted forms and the variants the
  character-skip approach additionally accepts (leading ellipsis/dots,
  runs of trailing punctuation), plus FixInvalidItalicTags integration
  tests pinning the resulting italic merges

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@niksedk
niksedk merged commit a0b8e7c into SubtitleEdit:main Aug 9, 2026
1 check passed
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.

3 participants