Skip to content

Keep slice-of-slice and split segments zero-copy#2720

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf/sliced-string-increments
Jul 21, 2026
Merged

Keep slice-of-slice and split segments zero-copy#2720
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf/sliced-string-increments

Conversation

@lahma

@lahma lahma commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Part of the V8-gap performance campaign (#1775 follow-up). Two increments on the existing SlicedString zero-copy machinery.

Slice-of-slice stays zero-copy

str.slice(a,b).slice(c,d) materialized the intermediate view: Slice/Substring/Substr passed s.ToString() into CreateSliced. A new internal JsString.CreateSliced(JsString source, start, length) overload rebases a not-yet-materialized SlicedString receiver directly onto its original backing string — and evaluates the retention policy against that original source length, so chained views cannot compound the pinning of a large backing string. Materialized views and all other receivers (including CustomString subclasses) take today's flat path.

Split cheapening

SplitWithStringSeparator routes every segment and the tail through CreateSliced, letting the policy decide copy vs view per segment, and split("") gains a fast path building an exact-capacity array straight from the cached single-char JsStrings.

Numbers (default BDN job, A/B vs current main, i.e. after #2717)

Row main this PR delta
SliceOfSlice (new row) 106.9 ms / 977 MB 0.77 ms / 469 KB −99.3% / −99.95% alloc
SubstringLargeDiscard 908.3 us 784.4 us −13.6%
SliceSmall 1,322 us 1,249 us −5.5%
SliceLargeDiscard / SliceThenRead flat
SearchOnSlice / SearchOnFlat statistically tied (launchCount-5 verified; this row is layout-bimodal with σ ≈ 5–6 ms across launches on both sides)

Gates

Jint.Tests 3661/3598 (net10/net472), PublicInterface 114/114 (CustomString non-materialization tripwire green), Test262 99,431 passed / 0 failed. Rebased on current main (includes #2716#2718); new tests cover chained-slice value equality across slice/substring/substr, view retention policy against the original source, split("") for ASCII/BMP/surrogate pairs, and split segment/tail correctness.

🤖 Generated with Claude Code

Two zero-copy String optimizations building on the existing SlicedString view:

- Add JsString.CreateSliced(JsString, int, int) that rebases a slice of an
  un-materialized SlicedString directly onto its original backing string,
  evaluating the retention policy against that original source so chained views
  cannot compound the pinning of a large backing string. A materialized view or
  any other receiver falls back to the flat path. Slice/Substring/Substr now
  pass the JsString receiver instead of s.ToString(), so a slice-of-slice never
  materializes the intermediate view. The copy fallback now routes through
  Create so short results still hit the cached single-char / empty instances.

- Split now routes each segment and the tail through CreateSliced (policy
  decides copy vs view against the source length), and gains a dedicated
  split("") fast path that builds an exact-capacity array from cached single-char
  JsStrings, skipping the intermediate segment list. Segment list element type
  changed from string to JsString accordingly.

Custom JsString subclasses keep the existing plain path (no new materialization).
Adds a SliceOfSlice benchmark and unit tests for slice-of-slice correctness,
split("") on ASCII/non-ASCII, split tail/segment correctness, and the retention
policy still copying small results.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lahma
lahma merged commit e2abea5 into sebastienros:main Jul 21, 2026
4 checks passed
@lahma
lahma deleted the perf/sliced-string-increments branch July 21, 2026 11:45
lahma added a commit that referenced this pull request Jul 22, 2026
…tring substrings (#2740)

Three review follow-ups to the #2720 zero-copy slices:

- JsString.Equals(JsString) gets a length pre-check: comparing a short
  string against a large unmaterialized view materialized the whole
  view just to fail the compare. Every Length override answers without
  materializing.
- substring()/substr() gain the whole-string identity shortcut slice()
  already had: strings are immutable, so s.substring(0) returns the
  receiver instead of allocating a duplicate wrapper or view.
- CreateSliced's whole-string branch routes through Create so 0/1-char
  sources (e.g. ''.split('x') segments) reuse the cached instances.


Claude-Session: https://claude.ai/code/session_0115tQFNyyQqc1HQGPLUgZND

Co-authored-by: Claude Fable 5 <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