From 68a2faf0ef5adf6d7d14535a65ff3c4c9beb121f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 05:03:00 +0000 Subject: [PATCH] perf: optimize dsl string allocation in std.web Refactored `dsl_to_html` to utilize a single mutable String buffer during recursive DSL block rendering, avoiding intermediate string allocations. Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com> --- .jules/bolt.md | 3 + stdlib/src/web.rs | 240 ++++++++++++++++++++++++---------------------- 2 files changed, 127 insertions(+), 116 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index cfddb5c1..817bbbb5 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -29,3 +29,6 @@ ## 2026-09-20 - String allocation optimization in stdlib json stringify **Learning:** Generating large JSON structures heavily penalizes performance if intermediate strings are allocated inside loops, specifically using `format!` and joining `Vec`. Converting string builders to pass down a mutable `&mut String` buffer to sub-functions significantly improves performance and reduces heap allocations. **Action:** Use recursive string builders holding a mutable reference to a `String` inside JSON or tree serialization tasks instead of allocating and returning new `String`s for every sub-node. +## 2026-09-22 - String allocation optimization in std.web DSL rendering +**Learning:** Generating deep HTML structures in `std.web` heavily penalized performance because `dsl_to_html` allocated and returned a new `String` for every child DSL node. This causes `O(N)` heap allocations and redundant copying in the render tree. By passing a mutable `&mut String` buffer recursively downwards, we avoid all intermediate string heap allocations and significantly improve serialization speed. +**Action:** Always prefer using a recursive builder pattern passing a single mutable `&mut String` buffer to `write!` or `push_str` when rendering nested tree structures (like HTML, JSON, or ASTs) instead of returning newly allocated strings at each layer. diff --git a/stdlib/src/web.rs b/stdlib/src/web.rs index 55063e01..2c2040db 100644 --- a/stdlib/src/web.rs +++ b/stdlib/src/web.rs @@ -98,9 +98,10 @@ static PAGE_CONTENT: Mutex = Mutex::new(String::new()); fn render_children(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue) { for child in &dsl.children { - html.push_str(&dsl_to_html( + dsl_to_html_inner( &techscript_runtime::value::RuntimeValue::DslBlock(std::rc::Rc::new(child.clone())), - )); + html, + ); } } @@ -233,129 +234,136 @@ fn render_card(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue /// Convert a DslBlockValue tree to HTML string. fn dsl_to_html(val: &RuntimeValue) -> String { + // ⚡ Bolt Performance Optimization: + // This function previously allocated and returned a new String for every single DSL node, + // causing heavy heap allocations when rendering deeply nested HTML structures. + // By passing a single mutable `String` buffer downwards via `dsl_to_html_inner`, + // we eliminate intermediate string heap allocations and significantly improve rendering speed. + let mut html = String::new(); + dsl_to_html_inner(val, &mut html); + html +} + +fn dsl_to_html_inner(val: &RuntimeValue, html: &mut String) { match val { - RuntimeValue::DslBlock(dsl) => { - let mut html = String::new(); - match dsl.kind.as_str() { - "website" => { - render_website(&mut html, dsl); - } - "page" => { - render_page(&mut html, dsl); - } - "hero" => { - render_hero(&mut html, dsl); - } - "section" => { - render_section(&mut html, dsl); - } - "card" => { - render_card(&mut html, dsl); - } - "button" => { - let label = dsl - .properties - .iter() - .find(|p| p.name == "label") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "Button".to_string()); - let _ = write!(html, "", label); - } - "link" => { - let label = dsl - .properties - .iter() - .find(|p| p.name == "label") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "Link".to_string()); - let url = dsl - .properties - .iter() - .find(|p| p.name == "url") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()); - if let Some(u) = url { - let _ = write!(html, "{}", u, label); - } else { - let _ = write!(html, "{}", label); - } - } - "nav" => { - html.push_str(""); + RuntimeValue::DslBlock(dsl) => match dsl.kind.as_str() { + "website" => { + render_website(html, dsl); + } + "page" => { + render_page(html, dsl); + } + "hero" => { + render_hero(html, dsl); + } + "section" => { + render_section(html, dsl); + } + "card" => { + render_card(html, dsl); + } + "button" => { + let label = dsl + .properties + .iter() + .find(|p| p.name == "label") + .and_then(|p| p.value.as_ref()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "Button".to_string()); + let _ = write!(html, "", label); + } + "link" => { + let label = dsl + .properties + .iter() + .find(|p| p.name == "label") + .and_then(|p| p.value.as_ref()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "Link".to_string()); + let url = dsl + .properties + .iter() + .find(|p| p.name == "url") + .and_then(|p| p.value.as_ref()) + .map(|v| v.to_string()); + if let Some(u) = url { + let _ = write!(html, "{}", u, label); + } else { + let _ = write!(html, "{}", label); } - "header" => { - html.push_str("
"); - for prop in &dsl.properties { - if prop.name == "title" { - if let Some(RuntimeValue::Str(t)) = &prop.value { - let _ = write!(html, "

{}

", t); - } + } + "nav" => { + html.push_str(""); + } + "header" => { + html.push_str("
"); + for prop in &dsl.properties { + if prop.name == "title" { + if let Some(RuntimeValue::Str(t)) = &prop.value { + let _ = write!(html, "

{}

", t); } } - render_children(&mut html, dsl); - html.push_str("
"); } - "footer" => { - html.push_str("
"); - for prop in &dsl.properties { - if prop.name == "text" { - if let Some(RuntimeValue::Str(t)) = &prop.value { - let _ = write!(html, "

{}

", t); - } + render_children(html, dsl); + html.push_str("
"); + } + "footer" => { + html.push_str("
"); + for prop in &dsl.properties { + if prop.name == "text" { + if let Some(RuntimeValue::Str(t)) = &prop.value { + let _ = write!(html, "

{}

", t); } } - render_children(&mut html, dsl); - html.push_str("
"); - } - "input" => { - let placeholder = dsl - .properties - .iter() - .find(|p| p.name == "placeholder") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()); - if let Some(p) = placeholder { - let _ = write!(html, "", p); - } else { - html.push_str(""); - } - } - "form" => { - html.push_str("
"); - render_children(&mut html, dsl); - html.push_str("
"); - } - "main" => { - html.push_str("
"); - render_children(&mut html, dsl); - html.push_str("
"); - } - "aside" => { - html.push_str(""); } - "start" => { - let label = dsl - .properties - .iter() - .find(|p| p.name == "label") - .and_then(|p| p.value.as_ref()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "Get Started".to_string()); - let _ = write!(html, "{}", label); - } - _ => { - let _ = write!(html, "", dsl.kind); + render_children(html, dsl); + html.push_str(""); + } + "input" => { + let placeholder = dsl + .properties + .iter() + .find(|p| p.name == "placeholder") + .and_then(|p| p.value.as_ref()) + .map(|v| v.to_string()); + if let Some(p) = placeholder { + let _ = write!(html, "", p); + } else { + html.push_str(""); } } - html - } - _ => String::new(), + "form" => { + html.push_str("
"); + render_children(html, dsl); + html.push_str("
"); + } + "main" => { + html.push_str("
"); + render_children(html, dsl); + html.push_str("
"); + } + "aside" => { + html.push_str(""); + } + "start" => { + let label = dsl + .properties + .iter() + .find(|p| p.name == "label") + .and_then(|p| p.value.as_ref()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "Get Started".to_string()); + let _ = write!(html, "{}", label); + } + _ => { + let _ = write!(html, "", dsl.kind); + } + }, + _ => {} } } @@ -582,7 +590,7 @@ impl StdlibRegistry { }; let mut html = String::new(); for block in &blocks { - html.push_str(&dsl_to_html(block)); + dsl_to_html_inner(block, &mut html); } Ok(RuntimeValue::Str(html)) },