Skip to content

Commit b9bbd80

Browse files
authored
Merge pull request #663 from cmrschwarz/zero_alloc_indent_write
Zero alloc indent write
2 parents 080b5e0 + cace44d commit b9bbd80

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

benches/bench.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate serde_derive;
55

66
use criterion::Criterion;
77
use handlebars::{to_json, Context, Handlebars, Template};
8+
use serde_json::json;
89
use serde_json::value::Value as Json;
910
use std::collections::BTreeMap;
1011

@@ -211,12 +212,65 @@ fn large_nested_loop(c: &mut Criterion) {
211212
});
212213
}
213214

215+
fn deeply_nested_partial(c: &mut Criterion) {
216+
use std::iter::repeat;
217+
let mut handlebars = Handlebars::new();
218+
219+
handlebars
220+
.register_partial(
221+
"nested_partial",
222+
r#"{{#each baz}}
223+
<div class="nested">
224+
{{this}}{{#if (not @last)}}++{{/if}}
225+
</div>
226+
{{/each}}"#,
227+
)
228+
.expect("Invalid template format");
229+
230+
handlebars
231+
.register_partial(
232+
"partial",
233+
r#"
234+
<div class="partial">
235+
{{#each bar}}
236+
{{>nested_partial}}
237+
{{/each}}
238+
</div>"#,
239+
)
240+
.expect("Invalid template format");
241+
242+
handlebars
243+
.register_template_string(
244+
"test",
245+
r#"
246+
<div class="test">
247+
{{#each foo}}
248+
{{>partial}}
249+
{{/each}}
250+
</div>"#,
251+
)
252+
.expect("Invalid template format");
253+
254+
let data = json!({
255+
"foo": repeat(json!({
256+
"bar": repeat(json!({
257+
"baz": repeat("xyz").take(7).collect::<Vec<_>>()
258+
})).take(7).collect::<Vec<_>>()
259+
})).take(7).collect::<Vec<_>>()
260+
});
261+
262+
let ctx = Context::wraps(data).unwrap();
263+
c.bench_function("deeply_nested_partial", move |b| {
264+
b.iter(|| handlebars.render_with_context("test", &ctx).ok().unwrap());
265+
});
266+
}
267+
214268
#[cfg(unix)]
215269
criterion_group!(
216270
name = benches;
217271
config = profiled();
218272
targets = parse_template, render_template, large_loop_helper, large_loop_helper_with_context_creation,
219-
large_nested_loop
273+
large_nested_loop, deeply_nested_partial
220274
);
221275

222276
#[cfg(not(unix))]
@@ -226,7 +280,8 @@ criterion_group!(
226280
render_template,
227281
large_loop_helper,
228282
large_loop_helper_with_context_creation,
229-
large_nested_loop
283+
large_nested_loop,
284+
deeply_nested_partial
230285
);
231286

232287
criterion_main!(benches);

src/render.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,7 @@ pub fn indent_aware_write(
882882
}
883883

884884
if let Some(indent) = rc.get_indent_string() {
885-
out.write(support::str::with_indent(v, indent).as_ref())?;
885+
support::str::write_indented(v, indent, out)?;
886886
} else {
887887
out.write(v.as_ref())?;
888888
}

src/support.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
pub mod str {
22
use std::io::{Result, Write};
33

4+
use crate::Output;
5+
46
#[derive(Debug)]
57
pub struct StringWriter {
68
buf: Vec<u8>,
@@ -70,6 +72,24 @@ pub mod str {
7072
output
7173
}
7274

75+
/// like `with_indent`, but writing straight into the output
76+
pub fn write_indented(s: &str, indent: &str, w: &mut dyn Output) -> std::io::Result<()> {
77+
let mut i = 0;
78+
let len = s.len();
79+
loop {
80+
let Some(next_newline) = s[i..].find('\n') else {
81+
w.write(&s[i..])?;
82+
return Ok(());
83+
};
84+
w.write(&s[i..i + next_newline + 1])?;
85+
i += next_newline + 1;
86+
if i == len {
87+
return Ok(());
88+
}
89+
w.write(indent)?;
90+
}
91+
}
92+
7393
#[inline]
7494
pub(crate) fn whitespace_matcher(c: char) -> bool {
7595
c == ' ' || c == '\t'

0 commit comments

Comments
 (0)