From 776d467334e78a7535b1603d71faee60df8000cb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:46:57 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20string=20concate?= =?UTF-8?q?nation=20in=20interpreter?= 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/interpreter/mod.rs | 10 ++++++++-- src/interpreter/value.rs | 12 ++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index afe8117e..229fa561 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. + +## 2026-05-18 - [Optimize string concatenation in interpreter] +**Learning:** Using `format!("{a}{b}")` for string concatenation in the interpreter causes unnecessary allocations and overhead due to the `Display` trait implementation. +**Action:** Implement a `to_string_fast` method on the `Value` enum to return a `std::borrow::Cow<'_, str>` for fast path string conversions. Use `String::with_capacity` and `push_str` with the `to_string_fast` outputs to concatenate strings efficiently. diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 4e4bdd6f..3d0fae71 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -7564,11 +7564,17 @@ impl Interpreter { Ok(Value::Text(Arc::from(s))) } (Value::Text(a), b) => { - let result = format!("{a}{b}"); + let b_str = b.to_string_fast(); + let mut result = String::with_capacity(a.len() + b_str.len()); + result.push_str(a.as_ref()); + result.push_str(b_str.as_ref()); Ok(Value::Text(Arc::from(result.as_str()))) } (a, Value::Text(b)) => { - let result = format!("{a}{b}"); + let a_str = a.to_string_fast(); + let mut result = String::with_capacity(a_str.len() + b.len()); + result.push_str(a_str.as_ref()); + result.push_str(b.as_ref()); Ok(Value::Text(Arc::from(result.as_str()))) } (a, b) => Err(RuntimeError::new( diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 7d033a6a..d9127f8d 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -157,6 +157,18 @@ pub struct ActionSignature { } impl Value { + pub fn to_string_fast(&self) -> std::borrow::Cow<'_, str> { + match self { + Value::Text(s) => std::borrow::Cow::Borrowed(s.as_ref()), + Value::Number(n) => std::borrow::Cow::Owned(n.to_string()), + Value::Bool(b) => std::borrow::Cow::Borrowed(if *b { "yes" } else { "no" }), + Value::Null => std::borrow::Cow::Borrowed("null"), + Value::Nothing => std::borrow::Cow::Borrowed("nothing"), + // For complex types, just fallback to format! + _ => std::borrow::Cow::Owned(format!("{}", self)), + } + } + pub fn type_name(&self) -> &'static str { match self { Value::Number(_) => "Number",