diff --git a/.jules/bolt.md b/.jules/bolt.md index 3311e44d..1d8c6be7 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -14,3 +14,6 @@ ## 2024-05-24 - Avoid `push_str(&format!(...))` in loops **Learning:** Using `push_str(&format!(...))` inside loops creates unnecessary temporary String allocations because `format!` allocates a new `String` which is then appended and immediately dropped. **Action:** Use `std::fmt::Write` and the `write!` macro directly onto the target string buffer instead. +## 2025-02-12 - String allocation optimization +**Learning:** In heavily used loops, especially for rendering or stringifying tasks, `push_str(&format!(...))` allocates temporary strings unnecessarily. Replacing it with `write!` directly onto the mutable `String` buffer (using `std::fmt::Write`) skips the intermediate allocations and improves performance significantly. +**Action:** Use `std::fmt::Write` and the `write!` macro instead of `push_str(&format!(...))` when generating strings in loops to prevent temporary allocations and improve performance. diff --git a/stdlib/src/web.rs b/stdlib/src/web.rs index 51c80510..500b9235 100644 --- a/stdlib/src/web.rs +++ b/stdlib/src/web.rs @@ -1,5 +1,6 @@ use crate::{StdFunction, StdlibModule, StdlibRegistry}; use std::collections::HashMap; +use std::fmt::Write; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -109,7 +110,7 @@ fn render_website(html: &mut String, dsl: &techscript_runtime::value::DslBlockVa match prop.name.as_str() { "title" => { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("{}", t)); + let _ = write!(html, "{}", t); } } _ => {} @@ -148,12 +149,12 @@ fn render_website(html: &mut String, dsl: &techscript_runtime::value::DslBlockVa fn render_page(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue) { html.push_str("
"); if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = dsl.args.first() { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } for prop in &dsl.properties { if prop.name == "title" { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } } } @@ -167,12 +168,12 @@ fn render_hero(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue match prop.name.as_str() { "title" => { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } } "subtitle" => { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } } _ => {} @@ -188,12 +189,12 @@ fn render_section(html: &mut String, dsl: &techscript_runtime::value::DslBlockVa match prop.name.as_str() { "title" => { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } } "id" => { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("", t)); + let _ = write!(html, "", t); } } _ => {} @@ -211,17 +212,17 @@ fn render_card(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue match prop.name.as_str() { "title" => { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } } "text" => { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } } "image" => { if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("\"card", t)); + let _ = write!(html, "\"card", t); } } _ => {} @@ -259,7 +260,7 @@ fn dsl_to_html(val: &RuntimeValue) -> String { .and_then(|p| p.value.as_ref()) .map(|v| v.to_string()) .unwrap_or_else(|| "Button".to_string()); - html.push_str(&format!("", label)); + let _ = write!(html, "", label); } "link" => { let label = dsl @@ -276,9 +277,9 @@ fn dsl_to_html(val: &RuntimeValue) -> String { .and_then(|p| p.value.as_ref()) .map(|v| v.to_string()); if let Some(u) = url { - html.push_str(&format!("{}", u, label)); + let _ = write!(html, "{}", u, label); } else { - html.push_str(&format!("{}", label)); + let _ = write!(html, "{}", label); } } "nav" => { @@ -291,7 +292,7 @@ fn dsl_to_html(val: &RuntimeValue) -> String { for prop in &dsl.properties { if prop.name == "title" { if let Some(RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } } } @@ -303,7 +304,7 @@ fn dsl_to_html(val: &RuntimeValue) -> String { for prop in &dsl.properties { if prop.name == "text" { if let Some(RuntimeValue::Str(t)) = &prop.value { - html.push_str(&format!("

{}

", t)); + let _ = write!(html, "

{}

", t); } } } @@ -318,7 +319,7 @@ fn dsl_to_html(val: &RuntimeValue) -> String { .and_then(|p| p.value.as_ref()) .map(|v| v.to_string()); if let Some(p) = placeholder { - html.push_str(&format!("", p)); + let _ = write!(html, "", p); } else { html.push_str(""); } @@ -346,13 +347,10 @@ fn dsl_to_html(val: &RuntimeValue) -> String { .and_then(|p| p.value.as_ref()) .map(|v| v.to_string()) .unwrap_or_else(|| "Get Started".to_string()); - html.push_str(&format!( - "{}", - label - )); + let _ = write!(html, "{}", label); } _ => { - html.push_str(&format!("", dsl.kind)); + let _ = write!(html, "", dsl.kind); } } html diff --git a/stdlib/src/xml.rs b/stdlib/src/xml.rs index c3a3920a..7ea890c4 100644 --- a/stdlib/src/xml.rs +++ b/stdlib/src/xml.rs @@ -2,6 +2,7 @@ use crate::{StdFunction, StdlibModule, StdlibRegistry}; use indexmap::IndexMap; use std::cell::RefCell; use std::collections::HashMap; +use std::fmt::Write; use std::rc::Rc; use techscript_runtime::{error::RuntimeError, value::RuntimeValue}; @@ -44,7 +45,7 @@ impl StdlibRegistry { if let RuntimeValue::Map { entries, .. } = &args[0] { for (k, v) in entries.borrow().iter() { let val_str = v.try_into_string().unwrap_or_default(); - result.push_str(&format!("<{}>{}", k, val_str, k)); + let _ = write!(result, "<{}>{}", k, val_str, k); } } Ok(RuntimeValue::Str(result)) diff --git a/stdlib/src/yaml.rs b/stdlib/src/yaml.rs index 92d9c527..84565181 100644 --- a/stdlib/src/yaml.rs +++ b/stdlib/src/yaml.rs @@ -2,6 +2,7 @@ use crate::{StdFunction, StdlibModule, StdlibRegistry}; use indexmap::IndexMap; use std::cell::RefCell; use std::collections::HashMap; +use std::fmt::Write; use std::rc::Rc; use techscript_runtime::{error::RuntimeError, value::RuntimeValue}; @@ -47,7 +48,7 @@ impl StdlibRegistry { if let RuntimeValue::Map { entries, .. } = &args[0] { for (k, v) in entries.borrow().iter() { let val_str = v.try_into_string().unwrap_or_default(); - result.push_str(&format!("{}: {}\n", k, val_str)); + let _ = write!(result, "{}: {}\n", k, val_str); } } Ok(RuntimeValue::Str(result))