From 32d8a8638918b4af8d853787bc530cb143053a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 20 Aug 2026 08:20:13 +0200 Subject: [PATCH 1/2] perf(runtime): reserve array join separator capacity --- changelog.d/8434-array-join-capacity.md | 5 ++++ .../perry-runtime/src/array/iter_methods.rs | 9 ++++++-- .../test_issue_8434_array_join_capacity.ts | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 changelog.d/8434-array-join-capacity.md create mode 100644 test-files/test_issue_8434_array_join_capacity.ts diff --git a/changelog.d/8434-array-join-capacity.md b/changelog.d/8434-array-join-capacity.md new file mode 100644 index 0000000000..95519402b8 --- /dev/null +++ b/changelog.d/8434-array-join-capacity.md @@ -0,0 +1,5 @@ +### Performance + +- Preallocate `Array.prototype.join`'s exact separator-byte floor instead of + growing its Rust assembly buffer from zero capacity. Element coercion remains + single-pass, so user `toString` side effects are not duplicated. diff --git a/crates/perry-runtime/src/array/iter_methods.rs b/crates/perry-runtime/src/array/iter_methods.rs index db3e02f247..db5f5a6af8 100644 --- a/crates/perry-runtime/src/array/iter_methods.rs +++ b/crates/perry-runtime/src/array/iter_methods.rs @@ -1118,8 +1118,13 @@ pub extern "C" fn js_array_join( std::str::from_utf8_unchecked(std::slice::from_raw_parts(sep_data, sep_len)) }; - // Build result string - let mut result = String::new(); + // Separators are an exact lower bound for the result. Reserving them + // up front avoids the zero-capacity growth ladder without speculatively + // coercing elements (which could run user code or mutate the array). + let separator_bytes = sep_str + .len() + .saturating_mul(length.saturating_sub(1) as usize); + let mut result = String::with_capacity(separator_bytes); for i in 0..length as usize { if i > 0 { result.push_str(sep_str); diff --git a/test-files/test_issue_8434_array_join_capacity.ts b/test-files/test_issue_8434_array_join_capacity.ts new file mode 100644 index 0000000000..c6ba300174 --- /dev/null +++ b/test-files/test_issue_8434_array_join_capacity.ts @@ -0,0 +1,23 @@ +const show = (label: string, value: string): void => { + console.log(`${label}:${value}|${value.length}`); +}; + +show("holes", new Array(3).join("|")); +show("empty", ["", "", ""].join("")); +show("unicode", ["A", "😀", "é"].join("·")); + +let calls = 0; +const values: unknown[] = []; +const allocating = { + toString(): string { + calls++; + for (let i = 0; i < 128; i++) { + ("allocation-" + i).repeat(32); + } + values[1] = "after"; + return "before"; + }, +}; +values.push(allocating, "original", "tail"); +show("coerce", values.join("/")); +console.log(`calls:${calls}`); From 195bfc9ace53d6cba9641960506539485a6f019f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Thu, 20 Aug 2026 08:32:44 +0200 Subject: [PATCH 2/2] chore: key changelog fragment to PR 8460 --- .../{8434-array-join-capacity.md => 8460-array-join-capacity.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{8434-array-join-capacity.md => 8460-array-join-capacity.md} (100%) diff --git a/changelog.d/8434-array-join-capacity.md b/changelog.d/8460-array-join-capacity.md similarity index 100% rename from changelog.d/8434-array-join-capacity.md rename to changelog.d/8460-array-join-capacity.md