1 #[macro_use]
2 extern crate serde_derive;
3 
4 use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
5 use tera::{Context, Template, Tera, Value};
6 
7 static VARIABLE_ONLY: &str = "{{product.name}}";
8 
9 static SIMPLE_TEMPLATE: &str = "
10 <html>
11   <head>
12     <title>{{ product.name }}</title>
13   </head>
14   <body>
15     <h1>{{ product.name }} - {{ product.manufacturer | upper }}</h1>
16     <p>{{ product.summary }}</p>
17     <p>£{{ product.price * 1.20 }} (VAT inc.)</p>
18     <p>Look at reviews from your friends {{ username }}</p>
19     <button>Buy!</button>
20   </body>
21 </html>
22 ";
23 
24 static SIMPLE_TEMPLATE_LIQUID: &str = "
25 <html>
26   <head>
27     <title>{{ product.name }}</title>
28   </head>
29   <body>
30     <h1>{{ product.name }} - {{ product.manufacturer | upcase }}</h1>
31     <p>{{ product.summary }}</p>
32     <p>£{{ product.price | times: 1.20 }} (VAT inc.)</p>
33     <p>Look at reviews from your friends {{ username }}</p>
34     <button>Buy!</button>
35   </body>
36 </html>
37 ";
38 
39 #[derive(Debug, Serialize)]
40 struct Product {
41     name: String,
42     manufacturer: String,
43     price: i32,
44     summary: String,
45 }
46 impl Product {
new() -> Product47     pub fn new() -> Product {
48         Product {
49             name: "Moto G".to_owned(),
50             manufacturer: "Motorala".to_owned(),
51             summary: "A phone".to_owned(),
52             price: 100,
53         }
54     }
55 }
56 
57 static PRODUCTS_YAML: &str = "
58 username: bob
59 product:
60   name: Moto G
61   manufacturer: Motorola
62   summary: A phone
63   price: 100
64 ";
65 
bench_parsing_basic_template(c: &mut Criterion)66 fn bench_parsing_basic_template(c: &mut Criterion) {
67     let mut group = c.benchmark_group("bench_parsing_basic_template");
68     group.bench_function(BenchmarkId::new("render", "tera"), |b| {
69         b.iter(|| Template::new("bench", None, SIMPLE_TEMPLATE));
70     });
71     group.bench_function(BenchmarkId::new("render", "liquid"), |b| {
72         let parser = liquid::ParserBuilder::with_stdlib().build().unwrap();
73         parser
74             .parse(SIMPLE_TEMPLATE_LIQUID)
75             .expect("benchmark template parsing failed");
76         b.iter(|| parser.parse(SIMPLE_TEMPLATE_LIQUID));
77     });
78     group.finish();
79 }
80 
bench_rendering_only_variable(c: &mut Criterion)81 fn bench_rendering_only_variable(c: &mut Criterion) {
82     let mut group = c.benchmark_group("bench_rendering_only_variable");
83     group.bench_function(BenchmarkId::new("render", "tera"), |b| {
84         let mut tera = Tera::default();
85         tera.add_raw_template("test.html", VARIABLE_ONLY).unwrap();
86         let mut context = Context::new();
87         context.insert("product", &Product::new());
88         context.insert("username", &"bob");
89 
90         b.iter(|| tera.render("test.html", &context));
91     });
92     group.bench_function(BenchmarkId::new("render", "liquid"), |b| {
93         let parser = liquid::ParserBuilder::with_stdlib().build().unwrap();
94         let template = parser
95             .parse(VARIABLE_ONLY)
96             .expect("Benchmark template parsing failed");
97 
98         let data: liquid::Object =
99             serde_yaml::from_str(PRODUCTS_YAML).expect("Benchmark object parsing failed");
100 
101         template.render(&data).unwrap();
102         b.iter(|| template.render(&data));
103     });
104     group.finish();
105 }
106 
bench_rendering_basic_template(c: &mut Criterion)107 fn bench_rendering_basic_template(c: &mut Criterion) {
108     let mut group = c.benchmark_group("bench_rendering_basic_templates");
109     group.bench_function(BenchmarkId::new("render", "tera"), |b| {
110         let mut tera = Tera::default();
111         tera.add_raw_template("bench.html", SIMPLE_TEMPLATE)
112             .unwrap();
113         let mut context = Context::new();
114         context.insert("product", &Product::new());
115         context.insert("username", &"bob");
116 
117         b.iter(|| tera.render("bench.html", &context));
118     });
119     group.bench_function(BenchmarkId::new("render", "liquid"), |b| {
120         let parser = liquid::ParserBuilder::with_stdlib().build().unwrap();
121         let template = parser
122             .parse(SIMPLE_TEMPLATE_LIQUID)
123             .expect("Benchmark template parsing failed");
124 
125         let data: liquid::Object =
126             serde_yaml::from_str(PRODUCTS_YAML).expect("Benchmark object parsing failed");
127 
128         template.render(&data).unwrap();
129         b.iter(|| template.render(&data));
130     });
131     group.finish();
132 }
133 
bench_huge_loop(c: &mut Criterion)134 fn bench_huge_loop(c: &mut Criterion) {
135     #[derive(Serialize)]
136     struct DataWrapper {
137         v: String,
138     }
139 
140     #[derive(Serialize)]
141     struct RowWrapper {
142         real: Vec<DataWrapper>,
143         dummy: Vec<DataWrapper>,
144     }
145     let real: Vec<DataWrapper> = (1..1000)
146         .map(|i| DataWrapper {
147             v: format!("n={}", i),
148         })
149         .collect();
150     let dummy: Vec<DataWrapper> = (1..1000)
151         .map(|i| DataWrapper {
152             v: format!("n={}", i),
153         })
154         .collect();
155     let rows = RowWrapper { real, dummy };
156 
157     let mut group = c.benchmark_group("bench_huge_loop");
158     group.bench_function(BenchmarkId::new("render", "tera"), |b| {
159         let mut tera = Tera::default();
160         tera.add_raw_templates(vec![(
161             "huge.html",
162             "{% for real in rows.real %}{{real.v}}{% endfor %}",
163         )])
164         .unwrap();
165         let mut context = Context::new();
166         context.insert("rows", &rows);
167 
168         b.iter(|| tera.render("huge.html", &context));
169     });
170     group.bench_function(BenchmarkId::new("render", "liquid"), |b| {
171         let parser = liquid::ParserBuilder::with_stdlib().build().unwrap();
172         let template = parser
173             .parse("{% for this in real%}{{this.v}}{% endfor %}")
174             .expect("Benchmark template parsing failed");
175 
176         let row_wrapper = liquid::to_object(&rows).unwrap();
177 
178         template.render(&row_wrapper).unwrap();
179         b.iter(|| template.render(&row_wrapper));
180     });
181     group.finish();
182 }
183 
deep_object() -> Value184 fn deep_object() -> Value {
185     let data = r#"{
186                     "foo": {
187                         "bar": {
188                             "goo": {
189                                 "moo": {
190                                     "cows": [
191                                         {
192                                             "name": "betsy",
193                                             "age" : 2,
194                                             "temperament": "calm"
195                                         },
196                                         {
197                                             "name": "elsie",
198                                             "age": 3,
199                                             "temperament": "calm"
200                                         },
201                                         {
202                                             "name": "veal",
203                                             "age": 1,
204                                             "temperament": "ornery"
205                                         }
206                                     ]
207                                 }
208                             }
209                         }
210                     }
211                   }"#;
212 
213     serde_json::from_str(data).unwrap()
214 }
215 
deep_object_liquid() -> liquid::Object216 fn deep_object_liquid() -> liquid::Object {
217     liquid::object!({
218         "deep_object": {
219             "foo": {
220                 "bar": {
221                     "goo": {
222                         "moo": {
223                             "cows": [
224                                 {
225                                     "name": "betsy",
226                                     "age" : 2,
227                                     "temperament": "calm"
228                                 },
229                                 {
230                                     "name": "elsie",
231                                     "age": 3,
232                                     "temperament": "calm"
233                                 },
234                                 {
235                                     "name": "veal",
236                                     "age": 1,
237                                     "temperament": "ornery"
238                                 }
239                             ]
240                         }
241                     }
242                 }
243             }
244         }
245     })
246 }
247 
bench_access_deep_object(c: &mut Criterion)248 fn bench_access_deep_object(c: &mut Criterion) {
249     let mut group = c.benchmark_group("bench_access_deep_object");
250     group.bench_function(BenchmarkId::new("render", "tera"), |b| {
251         let mut tera = Tera::default();
252         tera.add_raw_templates(vec![(
253             "deep_object.html",
254             "{% for cow in deep_object.foo.bar.goo.moo.cows %}{{cow.temperament}}{% endfor %}",
255         )])
256         .unwrap();
257         let mut context = Context::new();
258         context.insert("deep_object", &deep_object());
259         assert!(tera
260             .render("deep_object.html", &context)
261             .unwrap()
262             .contains("ornery"));
263 
264         b.iter(|| tera.render("deep_object.html", &context));
265     });
266     group.bench_function(BenchmarkId::new("render", "liquid"), |b| {
267         let parser = liquid::ParserBuilder::with_stdlib().build().unwrap();
268         let template = parser
269             .parse(
270                 "{% for cow in deep_object.foo.bar.goo.moo.cows %}{{cow.temperament}}{% endfor %}",
271             )
272             .expect("Benchmark template parsing failed");
273 
274         let data = deep_object_liquid();
275 
276         template.render(&data).unwrap();
277         b.iter(|| template.render(&data));
278     });
279     group.finish();
280 }
281 
bench_access_deep_object_with_literal(c: &mut Criterion)282 fn bench_access_deep_object_with_literal(c: &mut Criterion) {
283     let mut group = c.benchmark_group("bench_access_deep_object_with_literal");
284     group.bench_function(BenchmarkId::new("render", "tera"), |b| {
285         let mut tera = Tera::default();
286         tera.add_raw_templates(vec![(
287             "deep_object.html",
288             "
289 {% set goo = deep_object.foo['bar'][\"goo\"] %}
290 {% for cow in goo.moo.cows %}{{cow.temperament}}
291 {% endfor %}",
292         )])
293         .unwrap();
294         let mut context = Context::new();
295         context.insert("deep_object", &deep_object());
296         assert!(tera
297             .render("deep_object.html", &context)
298             .unwrap()
299             .contains("ornery"));
300 
301         b.iter(|| tera.render("deep_object.html", &context));
302     });
303     group.bench_function(BenchmarkId::new("render", "liquid"), |b| {
304         let parser = liquid::ParserBuilder::with_stdlib().build().unwrap();
305         let template = parser
306             .parse(
307                 "
308 {% assign goo = deep_object.foo['bar'][\"goo\"] %}
309 {% for cow in goo.moo.cows %}{{cow.temperament}}
310 {% endfor %}
311                 ",
312             )
313             .expect("Benchmark template parsing failed");
314 
315         let data = deep_object_liquid();
316 
317         template.render(&data).unwrap();
318         b.iter(|| template.render(&data));
319     });
320     group.finish();
321 }
322 
323 criterion_group!(
324     benches,
325     bench_parsing_basic_template,
326     bench_rendering_only_variable,
327     bench_rendering_basic_template,
328     bench_huge_loop,
329     bench_access_deep_object,
330     bench_access_deep_object_with_literal,
331 );
332 criterion_main!(benches);
333