Skip to content

Commit e74b543

Browse files
jasmith-hsclaude
andcommitted
Add JMH benchmarks for Jinjava rendering performance
The benchmark module was removed in PR #1018 because it was a disconnected directory that broke internal builds. This restores benchmarking as test-scoped JMH code within the existing project, so it compiles with the normal build and doesn't require a separate module. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent feddac9 commit e74b543

4 files changed

Lines changed: 249 additions & 0 deletions

File tree

pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<dep.algebra.version>1.5</dep.algebra.version>
2525
<dep.mockito.version>5.20.0</dep.mockito.version>
2626
<dep.jackson.version>2.20.1</dep.jackson.version>
27+
<dep.jmh.version>1.37</dep.jmh.version>
2728

2829
<basepom.test.add.opens>
2930
--add-opens=java.base/java.lang=ALL-UNNAMED
@@ -217,6 +218,16 @@
217218
<artifactId>mockito-core</artifactId>
218219
<scope>test</scope>
219220
</dependency>
221+
<dependency>
222+
<groupId>org.openjdk.jmh</groupId>
223+
<artifactId>jmh-core</artifactId>
224+
<scope>test</scope>
225+
</dependency>
226+
<dependency>
227+
<groupId>org.openjdk.jmh</groupId>
228+
<artifactId>jmh-generator-annprocess</artifactId>
229+
<scope>test</scope>
230+
</dependency>
220231
<dependency>
221232
<groupId>org.immutables</groupId>
222233
<artifactId>value</artifactId>
@@ -352,6 +363,11 @@
352363
<groupId>org.immutables</groupId>
353364
<artifactId>value</artifactId>
354365
</path>
366+
<path>
367+
<groupId>org.openjdk.jmh</groupId>
368+
<artifactId>jmh-generator-annprocess</artifactId>
369+
<version>${dep.jmh.version}</version>
370+
</path>
355371
</annotationProcessorPaths>
356372
</configuration>
357373
</plugin>
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package com.hubspot.jinjava.benchmarks;
2+
3+
import com.google.common.collect.ImmutableList;
4+
import com.google.common.collect.ImmutableMap;
5+
import com.google.common.io.Resources;
6+
import com.hubspot.jinjava.Jinjava;
7+
import com.hubspot.jinjava.JinjavaConfig;
8+
import java.io.IOException;
9+
import java.nio.charset.StandardCharsets;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.concurrent.TimeUnit;
14+
import org.openjdk.jmh.annotations.Benchmark;
15+
import org.openjdk.jmh.annotations.BenchmarkMode;
16+
import org.openjdk.jmh.annotations.Fork;
17+
import org.openjdk.jmh.annotations.Measurement;
18+
import org.openjdk.jmh.annotations.Mode;
19+
import org.openjdk.jmh.annotations.OutputTimeUnit;
20+
import org.openjdk.jmh.annotations.Scope;
21+
import org.openjdk.jmh.annotations.Setup;
22+
import org.openjdk.jmh.annotations.State;
23+
import org.openjdk.jmh.annotations.Warmup;
24+
import org.openjdk.jmh.runner.Runner;
25+
import org.openjdk.jmh.runner.RunnerException;
26+
import org.openjdk.jmh.runner.options.Options;
27+
import org.openjdk.jmh.runner.options.OptionsBuilder;
28+
29+
@State(Scope.Benchmark)
30+
@BenchmarkMode(Mode.Throughput)
31+
@OutputTimeUnit(TimeUnit.SECONDS)
32+
@Fork(1)
33+
@Warmup(iterations = 3, time = 3)
34+
@Measurement(iterations = 5, time = 3)
35+
public class JinjavaBenchmark {
36+
37+
private Jinjava jinjava;
38+
private String simpleTemplate;
39+
private String complexTemplate;
40+
private Map<String, Object> simpleBindings;
41+
private Map<String, Object> complexBindings;
42+
43+
@Setup
44+
public void setup() throws IOException {
45+
jinjava = new Jinjava();
46+
47+
simpleTemplate =
48+
Resources.toString(
49+
Resources.getResource("benchmarks/simple.jinja"),
50+
StandardCharsets.UTF_8
51+
);
52+
complexTemplate =
53+
Resources.toString(
54+
Resources.getResource("benchmarks/complex.jinja"),
55+
StandardCharsets.UTF_8
56+
);
57+
58+
List<List<String>> table = new ArrayList<>();
59+
for (int i = 0; i < 10; i++) {
60+
List<String> row = new ArrayList<>();
61+
for (int j = 0; j < 5; j++) {
62+
row.add("cell_" + i + "_" + j);
63+
}
64+
table.add(row);
65+
}
66+
67+
simpleBindings =
68+
ImmutableMap.of(
69+
"page_title",
70+
"Jinjava Benchmark",
71+
"navigation",
72+
ImmutableList.of(
73+
ImmutableMap.of("href", "index.html", "caption", "Index"),
74+
ImmutableMap.of("href", "downloads.html", "caption", "Downloads"),
75+
ImmutableMap.of("href", "products.html", "caption", "Products")
76+
),
77+
"table",
78+
table
79+
);
80+
81+
List<Map<String, Object>> users = ImmutableList.of(
82+
ImmutableMap.of("href", "/user/john", "username", "John Doe"),
83+
ImmutableMap.of("href", "/user/jane", "username", "Jane Doe"),
84+
ImmutableMap.of("href", "/user/peter", "username", "Peter Somewhat")
85+
);
86+
87+
List<Map<String, Object>> articles = new ArrayList<>();
88+
for (int i = 0; i < 20; i++) {
89+
articles.add(
90+
ImmutableMap
91+
.<String, Object>builder()
92+
.put("href", "/article/" + i)
93+
.put("title", "Article Title Number " + i)
94+
.put("user", users.get(i % users.size()))
95+
.put(
96+
"body",
97+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(5)
98+
)
99+
.put("pub_date", "2024-01-" + String.format("%02d", (i % 28) + 1))
100+
.put(
101+
"tags",
102+
ImmutableList.of("tag" + (i % 3), "tag" + (i % 5), "tag" + (i % 7))
103+
)
104+
.build()
105+
);
106+
}
107+
108+
complexBindings =
109+
ImmutableMap.of(
110+
"page_title",
111+
"Jinjava Complex Benchmark",
112+
"navigation",
113+
ImmutableList.of(
114+
ImmutableMap.of("href", "index.html", "caption", "Index"),
115+
ImmutableMap.of("href", "about.html", "caption", "About"),
116+
ImmutableMap.of("href", "contact.html", "caption", "Contact")
117+
),
118+
"users",
119+
users,
120+
"articles",
121+
articles
122+
);
123+
}
124+
125+
@Benchmark
126+
public String simpleTemplateBenchmark() {
127+
return jinjava.render(simpleTemplate, simpleBindings);
128+
}
129+
130+
@Benchmark
131+
public String complexTemplateBenchmark() {
132+
return jinjava.render(complexTemplate, complexBindings);
133+
}
134+
135+
@Benchmark
136+
public String simpleExpressionBenchmark() {
137+
return jinjava.render("Hello {{ name }}!", ImmutableMap.of("name", "World"));
138+
}
139+
140+
@Benchmark
141+
public String filterChainBenchmark() {
142+
return jinjava.render(
143+
"{{ value|capitalize|replace('foo', 'bar')|trim }}",
144+
ImmutableMap.of("value", " foo hello world ")
145+
);
146+
}
147+
148+
@Benchmark
149+
public Jinjava jinjavaCreationBenchmark() {
150+
return new Jinjava();
151+
}
152+
153+
@Benchmark
154+
public Jinjava jinjavaWithConfigCreationBenchmark() {
155+
return new Jinjava(
156+
JinjavaConfig.newBuilder().withMaxRenderDepth(20).withTrimBlocks(true).build()
157+
);
158+
}
159+
160+
public static void main(String[] args) throws RunnerException {
161+
Options options = new OptionsBuilder()
162+
.include(JinjavaBenchmark.class.getSimpleName())
163+
.build();
164+
new Runner(options).run();
165+
}
166+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>{{ page_title }}</title>
5+
</head>
6+
<body>
7+
<div class="header">
8+
<h1>{{ page_title }}</h1>
9+
</div>
10+
<ul class="navigation">
11+
{% for item in navigation %}
12+
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
13+
{% endfor %}
14+
</ul>
15+
<div class="content">
16+
{% for article in articles %}
17+
<div class="article">
18+
<h2><a href="{{ article.href }}">{{ article.title }}</a></h2>
19+
<p class="meta">by <a href="{{ article.user.href }}">{{ article.user.username }}</a> on {{ article.pub_date }}</p>
20+
<div class="body">{{ article.body }}</div>
21+
{% if article.tags %}
22+
<ul class="tags">
23+
{% for tag in article.tags %}
24+
<li><a href="/tag/{{ tag }}">{{ tag }}</a></li>
25+
{% endfor %}
26+
</ul>
27+
{% endif %}
28+
</div>
29+
{% endfor %}
30+
</div>
31+
<div class="sidebar">
32+
<h3>Users</h3>
33+
<ul>
34+
{% for user in users %}
35+
<li><a href="{{ user.href }}">{{ user.username }}</a></li>
36+
{% endfor %}
37+
</ul>
38+
</div>
39+
</body>
40+
</html>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>{{ page_title }}</title>
5+
</head>
6+
<body>
7+
<div class="header">
8+
<h1>{{ page_title }}</h1>
9+
</div>
10+
<ul class="navigation">
11+
{% for item in navigation %}
12+
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
13+
{% endfor %}
14+
</ul>
15+
<div class="table">
16+
<table>
17+
{% for row in table %}
18+
<tr>
19+
{% for cell in row %}
20+
<td>{{ cell }}</td>
21+
{% endfor %}
22+
</tr>
23+
{% endfor %}
24+
</table>
25+
</div>
26+
</body>
27+
</html>

0 commit comments

Comments
 (0)