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",