From 51808b9946f673d969c39d28fb776e34a61c113c Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Wed, 17 Jul 2024 19:08:25 +0200 Subject: [PATCH 1/2] add nested partials benchmark --- benches/bench.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 242e8ec70..4386cc44b 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -5,6 +5,7 @@ extern crate serde_derive; use criterion::Criterion; use handlebars::{to_json, Context, Handlebars, Template}; +use serde_json::json; use serde_json::value::Value as Json; use std::collections::BTreeMap; @@ -211,12 +212,65 @@ fn large_nested_loop(c: &mut Criterion) { }); } +fn deeply_nested_partial(c: &mut Criterion) { + use std::iter::repeat; + let mut handlebars = Handlebars::new(); + + handlebars + .register_partial( + "nested_partial", + r#"{{#each baz}} +
+ {{this}}{{#if (not @last)}}++{{/if}} +
+{{/each}}"#, + ) + .expect("Invalid template format"); + + handlebars + .register_partial( + "partial", + r#" +
+{{#each bar}} + {{>nested_partial}} +{{/each}} +
"#, + ) + .expect("Invalid template format"); + + handlebars + .register_template_string( + "test", + r#" +
+{{#each foo}} + {{>partial}} +{{/each}} +
"#, + ) + .expect("Invalid template format"); + + let data = json!({ + "foo": repeat(json!({ + "bar": repeat(json!({ + "baz": repeat("xyz").take(7).collect::>() + })).take(7).collect::>() + })).take(7).collect::>() + }); + + let ctx = Context::wraps(data).unwrap(); + c.bench_function("deeply_nested_partial", move |b| { + b.iter(|| handlebars.render_with_context("test", &ctx).ok().unwrap()); + }); +} + #[cfg(unix)] criterion_group!( name = benches; config = profiled(); targets = parse_template, render_template, large_loop_helper, large_loop_helper_with_context_creation, - large_nested_loop + large_nested_loop, deeply_nested_partial ); #[cfg(not(unix))] @@ -226,7 +280,8 @@ criterion_group!( render_template, large_loop_helper, large_loop_helper_with_context_creation, - large_nested_loop + large_nested_loop, + deeply_nested_partial ); criterion_main!(benches); From cace44dcf10954df44f39eb8126598edd7fa36b9 Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Wed, 17 Jul 2024 19:09:46 +0200 Subject: [PATCH 2/2] add zero allocation indenting function --- src/render.rs | 2 +- src/support.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/render.rs b/src/render.rs index d25c6113e..bffc29324 100644 --- a/src/render.rs +++ b/src/render.rs @@ -882,7 +882,7 @@ pub fn indent_aware_write( } if let Some(indent) = rc.get_indent_string() { - out.write(support::str::with_indent(v, indent).as_ref())?; + support::str::write_indented(v, indent, out)?; } else { out.write(v.as_ref())?; } diff --git a/src/support.rs b/src/support.rs index d9849df1b..150c0cbfa 100644 --- a/src/support.rs +++ b/src/support.rs @@ -1,6 +1,8 @@ pub mod str { use std::io::{Result, Write}; + use crate::Output; + #[derive(Debug)] pub struct StringWriter { buf: Vec, @@ -70,6 +72,24 @@ pub mod str { output } + /// like `with_indent`, but writing straight into the output + pub fn write_indented(s: &str, indent: &str, w: &mut dyn Output) -> std::io::Result<()> { + let mut i = 0; + let len = s.len(); + loop { + let Some(next_newline) = s[i..].find('\n') else { + w.write(&s[i..])?; + return Ok(()); + }; + w.write(&s[i..i + next_newline + 1])?; + i += next_newline + 1; + if i == len { + return Ok(()); + } + w.write(indent)?; + } + } + #[inline] pub(crate) fn whitespace_matcher(c: char) -> bool { c == ' ' || c == '\t'