Skip to content

Change the internal structure of JsString for performance#4455

Merged
hansl merged 7 commits intoboa-dev:mainfrom
hansl:profiler-string
Dec 19, 2025
Merged

Change the internal structure of JsString for performance#4455
hansl merged 7 commits intoboa-dev:mainfrom
hansl:profiler-string

Conversation

@hansl
Copy link
Copy Markdown
Contributor

@hansl hansl commented Oct 5, 2025

This is the first in a series of PR to improve the performance of strings in Boa.

The first step was to introduce a new type of strings, SliceString, which contains a strong pointer to another string and start/end indices. This allows for very fast slicing of strings. This initially came at a performance cost by having an enumeration of kinds of strings. An intermediate experiment was introduced to have the kind be a tag on the internal JsString pointer. This still came as a cost as it required bit operations to figure out which function to call.

Finally, I moved to using a vtable. This helped with many points:

  1. as fast as before. Before this PR, there was still a deref of a pointer when accessing internal fields.
  2. we can now introduce many other types (which will come in their separate PRs).
  3. this makes the code to clone/drop/as_str (and even construction) more streamline as each function is their own implementation.

The biggest drawback is now we are operating with a bit more pointer and unsafe code. I made sure that all code was passing both debug, release and miri tests on boa_string and boa_engine's builtins::string suite.

This vtable bring slightly better performance (depending on the test, about 90-110%, but overall 2-3% better than the main branch), even though we only directly improved string slicing operations.

The next suite of PRs are going to cover more string types:

  1. since strings are constant and unmutable, include the length in the vtable directly instead of relying on a function call.
  2. also because of the above, splitting latin1 and utf-16 string kinds to remove conditionals on a lot of sub functions.
  3. have a concat string that makes concatenation instant, similar to how slice improve concatenation

Also, there might still be some slicing that creates new strings from existing ones without using SliceString. Those can be improved greatly.

Please note the benches/scripts/strings/slice.js test is twice as fast with this branch than with main.

The performance table for this PR:

Test Main PR
Richards 218 233
DeltaBlue 234 244
Crypto 184 208
RayTrace 502 485
EarleyBoyer 595 584
RegExp 88.1 83
Splay 905 837
NavierStokes 413 467
--- --- ---
TOTAL 313 320

@github-actions
Copy link
Copy Markdown

github-actions bot commented Dec 10, 2025

Test262 conformance changes

Test result main count PR count difference
Total 52,598 52,598 0
Passed 49,385 49,385 0
Ignored 2,134 2,134 0
Failed 1,079 1,079 0
Panics 0 0 0
Conformance 93.89% 93.89% 0.00%

@codecov
Copy link
Copy Markdown

codecov bot commented Dec 10, 2025

Codecov Report

❌ Patch coverage is 71.36364% with 63 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.19%. Comparing base (6ddc2b4) to head (31d97ba).
⚠️ Report is 618 commits behind head on main.

Files with missing lines Patch % Lines
core/string/src/display.rs 0.00% 24 Missing ⚠️
core/string/src/code_point.rs 56.52% 10 Missing ⚠️
core/string/src/vtable/static.rs 50.00% 9 Missing ⚠️
core/string/src/vtable/slice.rs 79.41% 7 Missing ⚠️
core/string/src/lib.rs 89.09% 6 Missing ⚠️
core/string/src/common.rs 37.50% 5 Missing ⚠️
core/string/src/vtable/sequence.rs 95.34% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4455      +/-   ##
==========================================
+ Coverage   47.24%   57.19%   +9.95%     
==========================================
  Files         476      508      +32     
  Lines       46892    58027   +11135     
==========================================
+ Hits        22154    33190   +11036     
- Misses      24738    24837      +99     

☔ View full report in Codecov by Sentry.
📢 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.

@hansl hansl changed the title Try to change the internal structure of JsString for performance Change the internal structure of JsString for performance Dec 15, 2025
@hansl hansl marked this pull request as ready for review December 15, 2025 04:43
This is the first in a series of PR to improve the performance of strings in Boa.

The first step was to introduce a new type of strings, `SliceString`, which contains a strong pointer to another string and start/end indices. This allows for very fast slicing of strings. This initially came at a performance cost by having an enumeration of kinds of strings. An intermediate experiment was introduced to have the kind be a tag on the internal JsString pointer. This still came as a cost as it required bit operations to figure out which function to call.

Finally, I moved to using a `vtable`. This helped with many points:
1. as fast as before. Before this PR, there was still a deref of a pointer when accessing internal fields.
2. we can now introduce many other types (which will come in their separate PRs).
3. this makes the code to clone/drop/as_str (and even construction) more streamline as each function is their own implementation.
Copy link
Copy Markdown
Member

@nekevss nekevss left a comment

Choose a reason for hiding this comment

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

Reviewed! Had a couple comments and nits. Overall this is looking good.

Comment thread core/engine/src/builtins/string/mod.rs Outdated
Comment thread core/string/src/common.rs
StaticString::new(JsStr::latin1("until".as_bytes())),
StaticString::new(JsStr::latin1("since".as_bytes())),
StaticString::new(JsStr::latin1("equals".as_bytes())),
StaticString::new(JsStr::latin1("toZonedDateTime".as_bytes())),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thought: temporal will probably always be temporal feature flagged because of the dependency, so should we also be feature flagging the StaticStrings that we register.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wouldn't worry for now. Maybe we should move these constants to boa_engine where it's the only place they're used, then we should feature flag some. I don't think boa_string should have temporal feature flags, personally.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually that's very true.

Comment thread core/string/src/lib.rs Outdated
Comment thread core/string/src/lib.rs Outdated
Comment thread core/string/src/lib.rs Outdated
Comment thread core/string/src/lib.rs Outdated
Comment thread core/string/src/lib.rs Outdated
#[must_use]
pub fn into_raw(self) -> NonNull<RawJsString> {
ManuallyDrop::new(self).ptr
pub fn into_raw(self) -> NonNull<()> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thought: Is NonNull<()> the best type here.

I'm not sure having APIs that expose and accept a NonNull<()> is the best approach vs. casting to a specific new type (e.g. `NonNull``). This is not something I'm necessarily pushing for being changed. It mostly gave me a bit of a pause when reviewing this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed it to an opaque type.

@nekevss nekevss added A-Performance Performance related changes and issues A-Internal Changes that don't modify execution behaviour labels Dec 18, 2025
@hansl hansl requested a review from nekevss December 19, 2025 00:44
@hansl hansl enabled auto-merge December 19, 2025 04:05
Copy link
Copy Markdown
Member

@nekevss nekevss left a comment

Choose a reason for hiding this comment

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

Looks good to me

@hansl hansl added this pull request to the merge queue Dec 19, 2025
Merged via the queue into boa-dev:main with commit ee168e7 Dec 19, 2025
18 checks passed
@hansl hansl deleted the profiler-string branch December 19, 2025 04:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Internal Changes that don't modify execution behaviour A-Performance Performance related changes and issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants