From ccc6428fafb55f22fd360f477801f4ef0ed05f76 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 11:40:28 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[Optimize=20text=20replacem?= =?UTF-8?q?ent=20fast=20path]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: logbie <1138960+logbie@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ src/stdlib/text.rs | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index afe8117e..f3df0c23 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -61,3 +61,7 @@ ## 2026-04-22 - [Avoid string allocation on single-part split] **Learning:** Calling `.split(delimiter)` on a reference-counted string (`Arc`) and `.map()`ing the results into `Arc::from(s)` unconditionally creates a new allocation for every chunk. If the delimiter doesn't exist, the entire string is re-allocated unnecessarily. **Action:** When iterating over a split of a reference-counted string, explicitly check if `s.len() == text.len() && !text.is_empty()`. If it is, use `Arc::clone(&text)` to return another reference to the existing string, bypassing the allocation. + +## 2024-05-09 - [Optimize text replacement fast path] +**Learning:** `str::replace` in Rust always allocates a new `String` even when the target substring is not found, which is inefficient when dealing with reference-counted strings (`Arc`). +**Action:** When performing text replacements on reference-counted strings (like `Arc` in `Value::Text`), always add a fast-path check using `.contains()` before calling `.replace()`. If the substring is not found, return an `Arc::clone` of the original string to prevent unnecessary memory allocation by the standard library. diff --git a/src/stdlib/text.rs b/src/stdlib/text.rs index c9591d6b..91478d70 100644 --- a/src/stdlib/text.rs +++ b/src/stdlib/text.rs @@ -279,8 +279,16 @@ pub fn native_replace(args: Vec) -> Result { let text = expect_text(&args[0])?; let old = expect_text(&args[1])?; let new = expect_text(&args[2])?; - let result = text.replace(old.as_ref(), new.as_ref()); - Ok(Value::Text(Arc::from(result))) + + // Optimization: str::replace always allocates a new String even if the substring is not found. + // By checking contains first, we can reuse the Arc reference when no replacement is needed, + // avoiding a memory allocation. + if !text.contains(old.as_ref()) { + Ok(Value::Text(Arc::clone(&text))) + } else { + let result = text.replace(old.as_ref(), new.as_ref()); + Ok(Value::Text(Arc::from(result))) + } } pub fn native_last_index_of(args: Vec) -> Result {