Skip to content

Zero-copy SlicedString search (indexOf/startsWith/endsWith/includes)#2547

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:okojo-ws1-sliced-search
Jun 27, 2026
Merged

Zero-copy SlicedString search (indexOf/startsWith/endsWith/includes)#2547
lahma merged 1 commit into
sebastienros:mainfrom
lahma:okojo-ws1-sliced-search

Conversation

@lahma

@lahma lahma commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

SlicedString is a zero-copy view over a backing string (produced by slice/substring/substr for large results). It already overrides Equals/GetHashCode/AsSpan for zero-copy, but the search methods IndexOf/StartsWith/EndsWith/Contains were inherited from the base JsString, which routes through ToString()materializing (allocating) the whole substring on every search. A fresh large slice that is searched and then discarded therefore allocated its full content on each indexOf/startsWith/endsWith/includes call.

This makes the four base search methods virtual and overrides them in SlicedString to search directly over AsSpan(). Ordinal char comparison is binary/sequence equality, so the parameterless span overloads produce results identical to the base StringComparison.Ordinal paths (verified across all target frameworks).

Benchmark

Jint.Benchmark/StringSliceBenchmarks.csSearchOnSlice creates a fresh large slice and searches it each iteration (the shape that previously materialized on every call). Default BDN job, .NET 10, Ryzen 9 5950X, same-runtime worktree A/B:

Benchmark Baseline This PR Δ
SearchOnSlice — time 378.2 ms 27.5 ms 13.7× faster
SearchOnSlice — alloc 1,163,559 KB 395 KB −99.97%
SearchOnFlat (guard) — time 54.9 ms 50.6 ms within noise
SearchOnFlat (guard) — alloc 161 KB 161 KB unchanged
SliceLargeDiscard / SliceThenRead no regression

SearchOnFlat is a dispatch-bound guard searching a plain (non-view) JsString; making the methods virtual is below the noise floor (dynamic PGO devirtualizes the monomorphic call sites).

Correctness

  • New differential test StringTests.SlicedStringSearchMatchesFlatString compares sliced-view search against an identical flat string across many needles and positions, both before and after the view materializes.
  • dotnet test Jint.Tests: 3138 passed, 0 failed.
  • Test262 (strict + sloppy): 99260 passed, 0 failed.

🤖 Generated with Claude Code

…ith/includes)

SlicedString is a zero-copy view over a backing string, but it inherited the base
JsString search methods, which route through ToString() — materializing (allocating)
the whole substring on every indexOf/startsWith/endsWith/includes call. A fresh large
slice that is searched and then discarded therefore allocated its full content each time.

Make the four base search methods virtual and override them in SlicedString to search
directly over AsSpan(). Ordinal char comparison is binary/sequence equality, so the
parameterless span overloads produce results identical to the base StringComparison.Ordinal
paths (verified across all target frameworks).

Benchmark (StringSliceBenchmarks.SearchOnSlice — fresh slice + search per iteration):
  Allocation: 1,163,559 KB -> 395 KB (-99.97%)
  Time:       378 ms -> 27.5 ms (13.7x faster)

The flat-string search guard (SearchOnFlat, dispatch-bound) and the existing slice
benchmarks show no regression — the virtual keyword is below the noise floor because
dynamic PGO devirtualizes the monomorphic call sites.

Adds a differential correctness test (SlicedStringSearchMatchesFlatString) comparing
sliced-view search against an identical flat string across many needles and positions,
both before and after the view materializes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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