Skip to content

Commit

Permalink
Merge pull request #663 from cmrschwarz/zero_alloc_indent_write
Browse files Browse the repository at this point in the history
Zero alloc indent write
  • Loading branch information
sunng87 authored Jul 19, 2024
2 parents 080b5e0 + cace44d commit b9bbd80
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 3 deletions.
59 changes: 57 additions & 2 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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}}
<div class="nested">
{{this}}{{#if (not @last)}}++{{/if}}
</div>
{{/each}}"#,
)
.expect("Invalid template format");

handlebars
.register_partial(
"partial",
r#"
<div class="partial">
{{#each bar}}
{{>nested_partial}}
{{/each}}
</div>"#,
)
.expect("Invalid template format");

handlebars
.register_template_string(
"test",
r#"
<div class="test">
{{#each foo}}
{{>partial}}
{{/each}}
</div>"#,
)
.expect("Invalid template format");

let data = json!({
"foo": repeat(json!({
"bar": repeat(json!({
"baz": repeat("xyz").take(7).collect::<Vec<_>>()
})).take(7).collect::<Vec<_>>()
})).take(7).collect::<Vec<_>>()
});

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))]
Expand All @@ -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);
2 changes: 1 addition & 1 deletion src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())?;
}
Expand Down
20 changes: 20 additions & 0 deletions src/support.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod str {
use std::io::{Result, Write};

use crate::Output;

#[derive(Debug)]
pub struct StringWriter {
buf: Vec<u8>,
Expand Down Expand Up @@ -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'
Expand Down

0 comments on commit b9bbd80

Please # to comment.