Skip to content

Commit 9304407

Browse files
committed
perf(stringbuilder): reuse unshared reset buffer
1 parent 96a3bf2 commit 9304407

2 files changed

Lines changed: 43 additions & 5 deletions

File tree

builtin/stringbuilder_buffer.mbt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
struct StringBuilder {
1717
mut data : FixedArray[UInt16]
1818
mut len : Int
19+
mut buffer_is_shared : Bool
1920
}
2021

2122
///|
@@ -33,7 +34,7 @@ struct StringBuilder {
3334
pub fn StringBuilder::StringBuilder(size_hint? : Int = 0) -> StringBuilder {
3435
let initial = if size_hint < 1 { 1 } else { (size_hint + 1) / 2 }
3536
let data : FixedArray[UInt16] = FixedArray::make(initial, 0)
36-
{ data, len: 0 }
37+
{ data, len: 0, buffer_is_shared: false }
3738
}
3839

3940
///|
@@ -84,6 +85,7 @@ fn StringBuilder::grow(self : StringBuilder, required : Int) -> Unit {
8485
len=self.len,
8586
)
8687
self.data = new_data
88+
self.buffer_is_shared = false
8789
}
8890

8991
///|
@@ -184,6 +186,9 @@ pub fn StringBuilder::to_string(self : StringBuilder) -> String {
184186
if self.len == 0 {
185187
""
186188
} else if self.len == self.data.length() {
189+
// This conversion can reuse `data` without copying, so future resets must
190+
// detach before the buffer is written again.
191+
self.buffer_is_shared = true
187192
unsafe_fixedarray_uint16_to_string(self.data)
188193
} else {
189194
let data = FixedArray::make_and_blit(
@@ -210,9 +215,14 @@ pub impl Show for StringBuilder with fn to_string(self) {
210215
///|
211216
/// Resets the string builder to an empty state.
212217
pub fn StringBuilder::reset(self : StringBuilder) -> Unit {
213-
self.data = FixedArray::make(
214-
self.data.length(),
215-
(Default::default() : UInt16),
216-
)
218+
// A full buffer may have been returned directly from `to_string`. Retain the
219+
// backing storage in every other case, since it only contains value types.
220+
if self.buffer_is_shared {
221+
self.data = FixedArray::make(
222+
self.data.length(),
223+
(Default::default() : UInt16),
224+
)
225+
self.buffer_is_shared = false
226+
}
217227
self.len = 0
218228
}

builtin/stringbuilder_wbtest.mbt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2026 International Digital Economy Academy
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
///|
16+
#cfg(not(target="js"))
17+
test "StringBuilder::reset reuses an unshared buffer" {
18+
let builder = StringBuilder(size_hint=64)
19+
let backing = builder.data
20+
builder.write_string("hello")
21+
let first = builder.to_string()
22+
builder.reset()
23+
builder.write_string("bye")
24+
assert_true(first == "hello")
25+
assert_true(backing[0] == ('b' : UInt16))
26+
assert_true(backing[1] == ('y' : UInt16))
27+
assert_true(backing[2] == ('e' : UInt16))
28+
}

0 commit comments

Comments
 (0)