Skip to content

perf(stringbuilder): reuse unshared reset buffer - #3843

Open
mizchi wants to merge 5 commits into
moonbitlang:mainfrom
mizchi:perf/stringbuilder-reset
Open

perf(stringbuilder): reuse unshared reset buffer#3843
mizchi wants to merge 5 commits into
moonbitlang:mainfrom
mizchi:perf/stringbuilder-reset

Conversation

@mizchi

@mizchi mizchi commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Reuse a StringBuilder backing buffer on reset() when it has not been exposed by the zero-copy to_string() path.
  • Track the exceptional full-buffer case with a private buffer_is_shared flag and detach only then.
  • Add a non-JS whitebox regression test that verifies reuse while retaining the snapshot contract.

Why

The native/wasm StringBuilder::reset implementation always allocated and zero-filled a new FixedArray[UInt16], even when to_string() had already copied the contents. This defeated the intended spare-capacity reuse.

A full buffer may be turned into a String without copying, so simply setting len = 0 would mutate a string previously returned by to_string(). The new flag preserves that case by allocating a fresh buffer only when necessary.

Benchmark

Existing benchmark: StringBuilder::reset reuse spare capacity n=4096. Lower is better.

Target Before After Improvement
native 75.06 µs 52.32 µs 30%
wasm-gc 95.90 µs 81.63 µs 15%

Validation

  • moon test builtin --target all --quiet
  • moon check builtin --target all
  • moon info builtin
  • moon fmt builtin/stringbuilder_buffer.mbt builtin/stringbuilder_wbtest.mbt

No public API changes.

@mizchi
mizchi marked this pull request as ready for review July 20, 2026 14:37
Copilot AI review requested due to automatic review settings July 20, 2026 14:37

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 the native/wasm builtin StringBuilder implementation by allowing reset() to reuse the existing UTF-16 backing buffer when it’s safe (i.e., when the buffer hasn’t been exposed via the zero-copy to_string() fast path), reducing allocations and zero-fills while preserving correctness.

Changes:

  • Add an internal buffer_is_shared flag to track when to_string() returns a zero-copy string backed by the builder’s buffer.
  • Update reset() to reuse the existing buffer unless it has been shared, in which case it detaches by allocating a fresh buffer.
  • Add a non-JS whitebox regression test validating that reset() reuses an unshared buffer.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
builtin/stringbuilder_buffer.mbt Track zero-copy sharing in to_string() and reuse backing storage in reset() when safe.
builtin/stringbuilder_wbtest.mbt Add a non-JS whitebox test that asserts buffer reuse behavior across reset().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

2 participants