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 {