Skip to content

perf(string): single-alloc join/repeat/replace_once with safety fixes - #5506

Closed
xcb3d wants to merge 3 commits into
boa-dev:mainfrom
xcb3d:perf/string-single-alloc-fix
Closed

perf(string): single-alloc join/repeat/replace_once with safety fixes#5506
xcb3d wants to merge 3 commits into
boa-dev:mainfrom
xcb3d:perf/string-single-alloc-fix

Conversation

@xcb3d

@xcb3d xcb3d commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Single-allocation fast paths for the boa_string hot paths (join / repeat / replace_once / index_of), plus the safety fixes found during maintainer review.

It changes the following:

  • Add JsString::repeat/repeat_str with exponential doubling and checked overflow handling; repeat(1) shares via clone
  • Add JsString::join with single allocation, empty-neutral Latin1 detection, zero-len early return and hoisted separator variant
  • Add JsString::replace_once/replace_once_at reusing index_of, checked total_len and empty-neutral encoding
  • Dedupe JsStr::index_of into shared same/mixed helpers with first-byte skip and debug asserts
  • Fix Array.prototype.join self-check to object identity only (join.call('a') no longer returns empty)
  • Fix unsafe pointer writes via &raw mut, document nan-boxed as_str lifetime, harden build_as_latin1 truncation docs
  • Intern trim results back to static strings; hoist pad filler variant; reuse replace position to avoid double search
  • Add coverage for join/index_of/replace_once/repeat/trim

Performance

Criterion boa_benches --bench scripts -- strings (release), same machine, baseline (upstream HEAD 665f0392) vs patched — 2 runs each, median:

Benchmark Scenario Before After Delta
strings/split Split 26k-char string, 1-char separator 3.65 ms 0.039 ms (39 µs) 🟢 ~93x faster
strings/replace 10,000 substring replacements 6.53 ms 5.69 ms 🟢 ~13% faster
strings/concat 10,000 += concatenations 2.87 ms 2.66 ms 🟢 ~7% faster
strings/slice 10,000 slices over 260 MB string 2.70 ms 2.55 ms 🟢 ~6% faster

Why: split("a") calls the 1-char index_of fast path (position() directly instead of windows().position()) thousands of times; replace benefits from the single-alloc replace_once + no double search (replace_once_at); concat/slice gain indirectly via zero-copy as_str and doubling repeat in setup.

Targeted JS micro-bench (debug binary, Date.now()): repeat-100k 3 ms → 1 ms (~3x), replace-match-10k 97 ms → 73 ms (~25%), concat-5k 44 ms → 38 ms (~14%).

Verification

  • cargo test -p boa_string --lib: 30/30 ✅ (4 new test fns)
  • cargo test -p boa_engine --lib: 1120/0 ✅
  • cargo clippy -p boa_string --all-targets, cargo clippy -p boa_engine --lib: clean ✅
  • cargo fmt --check, git diff --check: clean ✅
  • cargo +nightly miri test -p boa_string --lib: 30/30 ✅
  • cargo make run-ci (fmt + lint all/no-features): pass ✅

Machine config

  • CPU: 13th Gen Intel Core i7-13700H (14 cores / 20 threads, up to 5.0 GHz)
  • RAM: 31 GiB (+ 8 GiB swap)
  • OS: Ubuntu 24.04.4 LTS, kernel 7.0.0-30-generic x86_64
  • Toolchain: rustc 1.98.0 / cargo 1.98.0, Miri on nightly 1.100.0
  • Note: benches run back-to-back on the same machine; single-run noise is ±5–10% (criterion outliers observed), medians of 2 runs reported above.

- Add JsString::repeat/repeat_str with exponential doubling and
  checked overflow handling; repeat(1) shares via clone
- Add JsString::join with single allocation, empty-neutral Latin1
  detection, zero-len early return and hoisted separator variant
- Add JsString::replace_once/replace_once_at reusing index_of,
  checked total_len and empty-neutral encoding
- Dedupe JsStr::index_of into shared same/mixed helpers with
  first-byte skip and debug asserts
- Fix Array.prototype.join self-check to object identity only
  (join.call('a') no longer returns empty)
- Fix unsafe pointer writes via &raw mut, document nan-boxed
  as_str lifetime, harden build_as_latin1 truncation docs
- Intern trim results back to static strings; hoist pad filler
  variant; reuse replace position to avoid double search
- Add coverage for join/index_of/replace_once/repeat/trim
@xcb3d
xcb3d requested a review from a team as a code owner September 4, 2026 15:11
@github-actions github-actions Bot added Waiting On Review Waiting on reviews from the maintainers C-Tests Issues and PRs related to the tests. C-Builtins PRs and Issues related to builtins/intrinsics and removed Waiting On Review Waiting on reviews from the maintainers labels Sep 4, 2026
@github-actions github-actions Bot added this to the v0.23 milestone Sep 4, 2026
Use fully-qualified paths (crate::JsString, crate::JsValue) in
new as_str/repeat docs so cargo doc --document-private-items
passes with denied warnings.
@github-actions github-actions Bot added the Waiting On Review Waiting on reviews from the maintainers label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Test262 conformance changes

Test result main count PR count difference
Total 53,578 53,578 0
Passed 51,426 51,426 0
Ignored 1,648 1,648 0
Failed 504 504 0
Panics 0 0 0
Conformance 95.98% 95.98% 0.00%

Tested main commit: 665f03924a54e5162be227e7e909612e36f6e35a
Tested PR commit: a63a92f4cd5e691928c54355e98c55463f89c2cf
Compare commits: 665f039...a63a92f

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.04509% with 79 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.81%. Comparing base (6ddc2b4) to head (a63a92f).
⚠️ Report is 1050 commits behind head on main.

Files with missing lines Patch % Lines
core/engine/src/builtins/string/mod.rs 51.51% 48 Missing ⚠️
core/string/src/lib.rs 91.61% 14 Missing ⚠️
core/string/src/str.rs 85.36% 12 Missing ⚠️
core/engine/src/value/inner/legacy.rs 0.00% 4 Missing ⚠️
core/string/src/builder.rs 80.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #5506       +/-   ##
===========================================
+ Coverage   47.24%   62.81%   +15.57%     
===========================================
  Files         476      536       +60     
  Lines       46892    60616    +13724     
===========================================
+ Hits        22154    38077    +15923     
+ Misses      24738    22539     -2199     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jedel1043

Copy link
Copy Markdown
Member

This is fixing too many things at once; 800+ lines of new code (some of them being unsafe). Please split your optimizations into multiple PRs.

@jedel1043 jedel1043 closed this Sep 4, 2026
@github-actions github-actions Bot removed the Waiting On Review Waiting on reviews from the maintainers label Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-Builtins PRs and Issues related to builtins/intrinsics C-Tests Issues and PRs related to the tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants