Skip to content

Commit 3d93928

Browse files
committed
perf: Reduce string cloning
This change attempts to strike a balance between what parts of the liquid API need to be dynamic and static. This makes the assumption that all block, tag, and filter names are known statically and uses a `&'static str` to represent them. Old ``` running 6 tests test bench_parse_template ... bench: 24,715 ns/iter (+/- 1,966) test bench_parse_text ... bench: 2,610 ns/iter (+/- 248) test bench_parse_variable ... bench: 4,628 ns/iter (+/- 425) test bench_render_template ... bench: 9,049 ns/iter (+/- 598) test bench_render_text ... bench: 2,204 ns/iter (+/- 151) test bench_render_variable ... bench: 6,867 ns/iter (+/- 437) ``` New ``` running 6 tests test bench_parse_template ... bench: 22,625 ns/iter (+/- 1,986) test bench_parse_text ... bench: 826 ns/iter (+/- 58) test bench_parse_variable ... bench: 2,871 ns/iter (+/- 175) test bench_render_template ... bench: 6,938 ns/iter (+/- 614) test bench_render_text ... bench: 432 ns/iter (+/- 29) test bench_render_variable ... bench: 6,201 ns/iter (+/- 537) ``` In the long term, we want to stop cloning `LiquidOptions` on every parse and `globals` on every render. This change reduced the impact of the former. BREAKING CHANGE: The API now takes `&'static str` instead of `String` for block, tag, and filter names
1 parent c0eadd5 commit 3d93928

15 files changed

+83
-60
lines changed

src/compiler/options.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use super::NullInclude;
77

88
#[derive(Clone)]
99
pub struct LiquidOptions {
10-
pub blocks: HashMap<String, BoxedBlockParser>,
11-
pub tags: HashMap<String, BoxedTagParser>,
10+
pub blocks: HashMap<&'static str, BoxedBlockParser>,
11+
pub tags: HashMap<&'static str, BoxedTagParser>,
1212
pub include_source: Box<Include>,
1313
}
1414

src/compiler/parser.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ fn parse_expression(tokens: &[Token], options: &LiquidOptions) -> Result<Box<Ren
5353
result.extend(indexes);
5454
Ok(Box::new(result))
5555
}
56-
Some(&Token::Identifier(ref x)) if options.tags.contains_key(x) => {
57-
options.tags[x].parse(x, &tokens[1..], options)
56+
Some(&Token::Identifier(ref x)) if options.tags.contains_key(x.as_str()) => {
57+
options.tags[x.as_str()].parse(x, &tokens[1..], options)
5858
}
5959
None => Error::parser("expression", None),
6060
_ => {
@@ -169,12 +169,12 @@ fn parse_tag(iter: &mut Iter<Element>,
169169
let tag = &tokens[0];
170170
match *tag {
171171
// is a tag
172-
Token::Identifier(ref x) if options.tags.contains_key(x) => {
173-
options.tags[x].parse(x, &tokens[1..], options)
172+
Token::Identifier(ref x) if options.tags.contains_key(x.as_str()) => {
173+
options.tags[x.as_str()].parse(x, &tokens[1..], options)
174174
}
175175

176176
// is a block
177-
Token::Identifier(ref x) if options.blocks.contains_key(x) => {
177+
Token::Identifier(ref x) if options.blocks.contains_key(x.as_str()) => {
178178
// Collect all the inner elements of this block until we find a
179179
// matching "end<blockname>" tag. Note that there may be nested blocks
180180
// of the same type (and hence have the same closing delimiter) *inside*
@@ -203,7 +203,7 @@ fn parse_tag(iter: &mut Iter<Element>,
203203
};
204204
children.push(t.clone())
205205
}
206-
options.blocks[x].parse(x, &tokens[1..], &children, options)
206+
options.blocks[x.as_str()].parse(x, &tokens[1..], &children, options)
207207
}
208208

209209
ref x => Err(Error::Parser(format!("parse_tag: {:?} not implemented", x))),
@@ -270,7 +270,7 @@ pub fn split_block<'a>(tokens: &'a [Element],
270270
for (i, t) in tokens.iter().enumerate() {
271271
if let Element::Tag(ref args, _) = *t {
272272
match args[0] {
273-
Token::Identifier(ref name) if options.blocks.contains_key(name) => {
273+
Token::Identifier(ref name) if options.blocks.contains_key(name.as_str()) => {
274274
stack.push("end".to_owned() + name);
275275
}
276276

@@ -391,9 +391,10 @@ mod test_split_block {
391391

392392
fn options() -> LiquidOptions {
393393
let mut options = LiquidOptions::default();
394-
let blocks: HashMap<String, BoxedBlockParser> = ["comment", "for", "if"]
394+
let blocks: [&'static str; 3] = ["comment", "for", "if"];
395+
let blocks: HashMap<&'static str, BoxedBlockParser> = blocks
395396
.into_iter()
396-
.map(|name| (name.to_string(), (null_block as FnParseBlock).into()))
397+
.map(|name| (*name, (null_block as FnParseBlock).into()))
397398
.collect();
398399
options.blocks = blocks;
399400
options

src/interpreter/context.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub struct Context {
2929
cycles: HashMap<String, usize>,
3030

3131
// Public for backwards compatability
32-
filters: HashMap<String, BoxedValueFilter>,
32+
filters: HashMap<&'static str, BoxedValueFilter>,
3333
}
3434

3535
impl Context {
@@ -43,7 +43,7 @@ impl Context {
4343
self
4444
}
4545

46-
pub fn with_filters(mut self, filters: HashMap<String, BoxedValueFilter>) -> Self {
46+
pub fn with_filters(mut self, filters: HashMap<&'static str, BoxedValueFilter>) -> Self {
4747
self.filters = filters;
4848
self
4949
}
@@ -66,8 +66,8 @@ impl Context {
6666
Ok(Some(val))
6767
}
6868

69-
pub fn add_filter(&mut self, name: &str, filter: BoxedValueFilter) {
70-
self.filters.insert(name.to_owned(), filter);
69+
pub fn add_filter(&mut self, name: &'static str, filter: BoxedValueFilter) {
70+
self.filters.insert(name, filter);
7171
}
7272

7373
pub fn get_filter<'b>(&'b self, name: &str) -> Option<&'b FilterValue> {

src/parser.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ use super::Template;
1212

1313
#[derive(Default, Clone)]
1414
pub struct ParserBuilder {
15-
blocks: HashMap<String, compiler::BoxedBlockParser>,
16-
tags: HashMap<String, compiler::BoxedTagParser>,
17-
filters: HashMap<String, interpreter::BoxedValueFilter>,
15+
blocks: HashMap<&'static str, compiler::BoxedBlockParser>,
16+
tags: HashMap<&'static str, compiler::BoxedTagParser>,
17+
filters: HashMap<&'static str, interpreter::BoxedValueFilter>,
1818
include_source: Option<Box<compiler::Include>>,
1919
}
2020

@@ -129,20 +129,26 @@ impl ParserBuilder {
129129
}
130130

131131
/// Inserts a new custom block into the parser
132-
pub fn block<B: Into<compiler::BoxedBlockParser>>(mut self, name: &str, block: B) -> Self {
133-
self.blocks.insert(name.to_owned(), block.into());
132+
pub fn block<B: Into<compiler::BoxedBlockParser>>(mut self,
133+
name: &'static str,
134+
block: B)
135+
-> Self {
136+
self.blocks.insert(name, block.into());
134137
self
135138
}
136139

137140
/// Inserts a new custom tag into the parser
138-
pub fn tag<T: Into<compiler::BoxedTagParser>>(mut self, name: &str, tag: T) -> Self {
139-
self.tags.insert(name.to_owned(), tag.into());
141+
pub fn tag<T: Into<compiler::BoxedTagParser>>(mut self, name: &'static str, tag: T) -> Self {
142+
self.tags.insert(name, tag.into());
140143
self
141144
}
142145

143146
/// Inserts a new custom filter into the parser
144-
pub fn filter<F: Into<interpreter::BoxedValueFilter>>(mut self, name: &str, filter: F) -> Self {
145-
self.filters.insert(name.to_owned(), filter.into());
147+
pub fn filter<F: Into<interpreter::BoxedValueFilter>>(mut self,
148+
name: &'static str,
149+
filter: F)
150+
-> Self {
151+
self.filters.insert(name, filter.into());
146152
self
147153
}
148154

@@ -175,7 +181,7 @@ impl ParserBuilder {
175181
#[derive(Default, Clone)]
176182
pub struct Parser {
177183
options: compiler::LiquidOptions,
178-
filters: HashMap<String, interpreter::BoxedValueFilter>,
184+
filters: HashMap<&'static str, interpreter::BoxedValueFilter>,
179185
}
180186

181187
impl Parser {

src/tags/assign_tag.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ mod test {
4848

4949
fn options() -> LiquidOptions {
5050
let mut options = LiquidOptions::default();
51-
options.tags.insert("assign".to_owned(),
52-
(assign_tag as compiler::FnParseTag).into());
53-
options.blocks.insert("if".to_owned(),
54-
(tags::if_block as compiler::FnParseBlock).into());
55-
options.blocks.insert("for".to_owned(),
56-
(tags::for_block as compiler::FnParseBlock).into());
51+
options
52+
.tags
53+
.insert("assign", (assign_tag as compiler::FnParseTag).into());
54+
options
55+
.blocks
56+
.insert("if", (tags::if_block as compiler::FnParseBlock).into());
57+
options
58+
.blocks
59+
.insert("for", (tags::for_block as compiler::FnParseBlock).into());
5760
options
5861
}
5962

src/tags/capture_block.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ mod test {
6060

6161
fn options() -> LiquidOptions {
6262
let mut options = LiquidOptions::default();
63-
options.blocks.insert("capture".to_owned(),
64-
(capture_block as compiler::FnParseBlock).into());
63+
options
64+
.blocks
65+
.insert("capture", (capture_block as compiler::FnParseBlock).into());
6566
options
6667
}
6768

src/tags/case_block.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,9 @@ mod test {
147147

148148
fn options() -> LiquidOptions {
149149
let mut options = LiquidOptions::default();
150-
options.blocks.insert("case".to_owned(),
151-
(case_block as compiler::FnParseBlock).into());
150+
options
151+
.blocks
152+
.insert("case", (case_block as compiler::FnParseBlock).into());
152153
options
153154
}
154155

src/tags/comment_block.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ mod test {
3030

3131
fn options() -> LiquidOptions {
3232
let mut options = LiquidOptions::default();
33-
options.blocks.insert("comment".to_owned(),
34-
(comment_block as compiler::FnParseBlock).into());
33+
options
34+
.blocks
35+
.insert("comment", (comment_block as compiler::FnParseBlock).into());
3536
options
3637
}
3738

src/tags/cycle_tag.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ mod test {
8484

8585
fn options() -> LiquidOptions {
8686
let mut options = LiquidOptions::default();
87-
options.tags.insert("cycle".to_owned(),
88-
(cycle_tag as compiler::FnParseTag).into());
87+
options
88+
.tags
89+
.insert("cycle", (cycle_tag as compiler::FnParseTag).into());
8990
options
9091
}
9192

src/tags/for_block.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,9 @@ mod test {
234234

235235
fn options() -> LiquidOptions {
236236
let mut options = LiquidOptions::default();
237-
options.blocks.insert("for".to_owned(),
238-
(for_block as compiler::FnParseBlock).into());
237+
options
238+
.blocks
239+
.insert("for", (for_block as compiler::FnParseBlock).into());
239240
options
240241
}
241242

src/tags/if_block.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,10 @@ mod test {
165165
let mut options = LiquidOptions::default();
166166
options
167167
.blocks
168-
.insert("if".to_owned(), (if_block as compiler::FnParseBlock).into());
169-
options.blocks.insert("unless".to_owned(),
170-
(unless_block as compiler::FnParseBlock).into());
168+
.insert("if", (if_block as compiler::FnParseBlock).into());
169+
options
170+
.blocks
171+
.insert("unless", (unless_block as compiler::FnParseBlock).into());
171172
options
172173
}
173174

src/tags/include_tag.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@ mod test {
5858

5959
let mut options = LiquidOptions::default();
6060
options.include_source = Box::new(compiler::FilesystemInclude::new(include_path));
61-
options.tags.insert("include".to_owned(),
62-
(include_tag as compiler::FnParseTag).into());
63-
options.blocks.insert("comment".to_owned(),
61+
options
62+
.tags
63+
.insert("include", (include_tag as compiler::FnParseTag).into());
64+
options.blocks.insert("comment",
6465
(tags::comment_block as compiler::FnParseBlock).into());
65-
options.blocks.insert("if".to_owned(),
66-
(tags::if_block as compiler::FnParseBlock).into());
66+
options
67+
.blocks
68+
.insert("if", (tags::if_block as compiler::FnParseBlock).into());
6769
options
6870
}
6971

src/tags/interrupt_tags.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,18 @@ mod test {
5858

5959
fn options() -> LiquidOptions {
6060
let mut options = LiquidOptions::default();
61-
options.tags.insert("break".to_owned(),
62-
(break_tag as compiler::FnParseTag).into());
63-
options.tags.insert("continue".to_owned(),
64-
(continue_tag as compiler::FnParseTag).into());
65-
options.blocks.insert("for".to_owned(),
66-
(tags::for_block as compiler::FnParseBlock).into());
67-
options.blocks.insert("if".to_owned(),
68-
(tags::if_block as compiler::FnParseBlock).into());
61+
options
62+
.tags
63+
.insert("break", (break_tag as compiler::FnParseTag).into());
64+
options
65+
.tags
66+
.insert("continue", (continue_tag as compiler::FnParseTag).into());
67+
options
68+
.blocks
69+
.insert("for", (tags::for_block as compiler::FnParseBlock).into());
70+
options
71+
.blocks
72+
.insert("if", (tags::if_block as compiler::FnParseBlock).into());
6973
options
7074
}
7175

src/tags/raw_block.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ mod test {
4141

4242
fn options() -> LiquidOptions {
4343
let mut options = LiquidOptions::default();
44-
options.blocks.insert("raw".to_owned(),
45-
(raw_block as compiler::FnParseBlock).into());
44+
options
45+
.blocks
46+
.insert("raw", (raw_block as compiler::FnParseBlock).into());
4647
options
4748
}
4849

src/template.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use interpreter::Renderable;
88

99
pub struct Template {
1010
pub(crate) template: interpreter::Template,
11-
pub(crate) filters: HashMap<String, interpreter::BoxedValueFilter>,
11+
pub(crate) filters: HashMap<&'static str, interpreter::BoxedValueFilter>,
1212
}
1313

1414
impl Template {

0 commit comments

Comments
 (0)