diff --git a/.gitignore b/.gitignore
index a90279a..70360f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ target/
*.war
*.ear
.project
+.idea/
\ No newline at end of file
diff --git a/README.md b/README.md
index 9911e36..9b2ae32 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,13 @@ template-benchmark
JMH benchmark for popular Java template engines:
* [Freemarker](http://freemarker.org/)
-* [Mustache](https://github.com/spullara/mustache.java)
+* Mustache ([spullara](https://github.com/spullara/mustache.java) and [samskivert](https://github.com/samskivert/jmustachet) implementations)
* [Pebble](http://www.mitchellbosecke.com/pebble)
* [Rocker](https://github.com/fizzed/rocker)
* [Thymeleaf](http://www.thymeleaf.org/)
* [Trimou](http://trimou.org/)
* [Velocity](http://velocity.apache.org/)
+* [Groovy Template](http://groovy-lang.org/)
Running the benchmark
======================
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..75c6b91
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,52 @@
+jobs:
+- job: Linux
+ timeoutInMinutes: 0
+ pool:
+ vmImage: 'Ubuntu-16.04'
+
+ steps:
+ - task: Maven@3
+ inputs:
+ mavenPomFile: 'pom.xml'
+ mavenOptions: '-Xmx3072m'
+ javaHomeOption: 'JDKVersion'
+ jdkVersionOption: '1.8'
+ jdkArchitectureOption: 'x64'
+ publishJUnitResults: true
+ testResultsFiles: '**/surefire-reports/TEST-*.xml'
+ goals: 'package'
+
+ - script: |
+ cat /proc/cpuinfo; lscpu
+ cat /proc/meminfo; free -m
+ displayName: 'Show hardware info'
+
+ - script: |
+ less /proc/cpuinfo; lscpu
+ rm results.*
+ java -jar target/benchmarks.jar -rff results.csv -rf csv
+ cat results.csv
+ sed -i -E 's/com\.mitchellbosecke\.benchmark\.(\w+)\.benchmark/\1/g' results.csv
+ python generate-results-with-version.py
+ sed -i -E 's/Mustache_/Mustache /g' results.csv
+ cat results.csv
+ displayName: 'Exec benchmarks'
+
+
+ - script: sudo apt-get install -y gnuplot
+ displayName: 'Install gnuplot'
+
+ - script: gnuplot benchmark.plot
+ displayName: 'Generate results.png'
+
+ - task: CopyFiles@2
+ inputs:
+ contents: |
+ **/*.jar
+ results.*
+ targetFolder: '$(build.artifactStagingDirectory)'
+
+ - task: PublishBuildArtifacts@1
+ inputs:
+ artifactName: 'results'
+ pathToPublish: '$(build.artifactStagingDirectory)'
diff --git a/benchmark.plot b/benchmark.plot
index b5e5243..a1cef11 100644
--- a/benchmark.plot
+++ b/benchmark.plot
@@ -13,7 +13,7 @@ set datafile separator ','
# Output
set terminal pngcairo enhanced font "Verdana,9"
set output 'results.png'
-set grid
+set grid lw 0.5
set key off
set boxwidth 0.8 relative
@@ -26,5 +26,6 @@ set style line 2 lc rgb '#808080' lt 1
set border 3 back ls 2
set tics nomirror
-plot 'results.csv' every ::1 using 0:5:xticlabels(stringcolumn(1)[31:36]) with boxes ls 1,\
- 'results.csv' every ::1 using 0:($5 + 1500):(sprintf("%d",$5)) with labels
+plot 'results.csv' every ::1 using 0:5:xticlabels(stringcolumn(1)) with boxes ls 1,\
+ 'results.csv' every ::1 using 0:($5 + 1500):(sprintf("%d",$5)) with labels,\
+ 'results.csv' every ::1 using 0:5:6 with yerrorbars linestyle -1 lc rgb 'black' lw 1
diff --git a/generate-results-with-version.py b/generate-results-with-version.py
new file mode 100644
index 0000000..230c0b9
--- /dev/null
+++ b/generate-results-with-version.py
@@ -0,0 +1,35 @@
+from tempfile import NamedTemporaryFile
+import shutil
+import csv
+from xml.etree import ElementTree
+import subprocess
+
+
+with open('pom.xml', 'rt') as f:
+ groups = ElementTree.parse(f)
+
+root = groups.getroot()
+tree = root.findall('.//')
+tree = tree[6].findall('.//')
+versions = {}
+for node in tree:
+ key = node.tag[node.tag.index("}") + 1:]
+ versions[key] = node.text
+
+filename = 'results.csv'
+tempfile = NamedTemporaryFile(mode='w', delete=False)
+
+fields = ['Benchmark', 'Mode', 'Threads', 'Samples', 'Score', 'Score Error (99.9%)', 'Unit']
+
+with open(filename, 'r') as csvfile:#, tempfile:
+ reader = csv.DictReader(csvfile, fieldnames=fields, delimiter=',')
+ writer = csv.DictWriter(tempfile, fieldnames=fields)
+ for row in reader:
+ if row['Benchmark'] != 'Benchmark':
+ key = "%s.version" % (row['Benchmark'].lower())
+ version = versions[key]
+ row['Benchmark'] = row['Benchmark'] + " " + version
+ writer.writerow(row)
+
+shutil.move(tempfile.name, filename)
+exit()
diff --git a/pom.xml b/pom.xml
index 74e3a7b..90e7bbe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,17 +11,19 @@
UTF-8
- 1.7.1
+ 1.21
benchmarks
- 1.5.2
- 0.9.0
- 2.3.22
- 1.7
- 2.1.4.RELEASE
+ 3.0.6
+ 1.14
+ 0.9.5
+ 2.3.28
+ 2.0
+ 3.0.11.RELEASE
4.11
- 1.8.1.Final
- 0.9.0
+ 2.5.0.Final
+ 1.2.0
+ 2.5.4
@@ -46,8 +48,8 @@
maven-compiler-plugin
2.3.2
- 1.7
- 1.7
+ 1.8
+ 1.8
@@ -120,14 +122,19 @@
- com.mitchellbosecke
+ io.pebbletemplates
pebble
${pebble.version}
+
+ com.samskivert
+ jmustache
+ ${mustache_samskivert.version}
+
com.github.spullara.mustache.java
compiler
- ${mustache.version}
+ ${mustache_spullara.version}
org.freemarker
@@ -136,7 +143,7 @@
org.apache.velocity
- velocity
+ velocity-engine-core
${velocity.version}
@@ -154,6 +161,11 @@
rocker-runtime
${rocker.version}
+
+ org.codehaus.groovy
+ groovy-templates
+ ${groovy.version}
+
junit
diff --git a/results.csv b/results.csv
index 99fd2d2..6568473 100644
--- a/results.csv
+++ b/results.csv
@@ -1,8 +1,10 @@
-"Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit"
-"com.mitchellbosecke.benchmark.Freemarker.benchmark","thrpt",1,50.000000,16737.333340,184.040944,"ops/s"
-"com.mitchellbosecke.benchmark.Mustache.benchmark","thrpt",1,50.000000,23393.115757,159.003408,"ops/s"
-"com.mitchellbosecke.benchmark.Pebble.benchmark","thrpt",1,50.000000,35736.995844,248.860050,"ops/s"
-"com.mitchellbosecke.benchmark.Rocker.benchmark","thrpt",1,50.000000,41123.645420,191.949947,"ops/s"
-"com.mitchellbosecke.benchmark.Thymeleaf.benchmark","thrpt",1,50.000000,1519.092213,18.156292,"ops/s"
-"com.mitchellbosecke.benchmark.Trimou.benchmark","thrpt",1,50.000000,19081.925071,112.440326,"ops/s"
-"com.mitchellbosecke.benchmark.Velocity.benchmark","thrpt",1,50.000000,21553.362499,223.035824,"ops/s"
+Benchmark,Mode,Threads,Samples,Score,Score Error (99.9%),Unit
+Freemarker 2.3.28,thrpt,1,50,14375.008770,67.949534,ops/s
+Groovy 2.5.4,thrpt,1,50,9916.720420,310.450084,ops/s
+Mustache Samskivert 1.14,thrpt,1,50,9756.638916,201.174622,ops/s
+Mustache Spullara 0.9.5,thrpt,1,50,16561.498801,51.512659,ops/s
+Pebble 3.0.6,thrpt,1,50,19575.873030,372.340451,ops/s
+Rocker 1.2.0,thrpt,1,50,29665.500272,326.030892,ops/s
+Thymeleaf 3.0.11.RELEASE,thrpt,1,50,4547.712767,44.024793,ops/s
+Trimou 2.5.0.Final,thrpt,1,50,16102.162324,207.259233,ops/s
+Velocity 2.0,thrpt,1,50,16435.810422,124.612788,ops/s
diff --git a/results.png b/results.png
index c5b0329..e7a52c5 100644
Binary files a/results.png and b/results.png differ
diff --git a/src/main/java/com/mitchellbosecke/benchmark/Groovy.java b/src/main/java/com/mitchellbosecke/benchmark/Groovy.java
new file mode 100644
index 0000000..e7333aa
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/benchmark/Groovy.java
@@ -0,0 +1,58 @@
+package com.mitchellbosecke.benchmark;
+
+import groovy.lang.Writable;
+import groovy.text.Template;
+import groovy.text.markup.MarkupTemplateEngine;
+import groovy.text.markup.TemplateConfiguration;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Setup;
+
+import java.io.*;
+import java.util.Map;
+
+public class Groovy extends BaseBenchmark {
+
+ private Map context;
+
+ private Template template;
+
+ @Setup
+ public void setup() {
+ TemplateConfiguration config = new TemplateConfiguration();
+ config.setAutoEscape(false);
+ config.setUseDoubleQuotes(true);
+ MarkupTemplateEngine engine = new MarkupTemplateEngine(config);
+ InputStream is = this.getClass().getClassLoader()
+ .getResourceAsStream("templates/stocks.groovy.tpl");
+ Reader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(is, "UTF8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException(e);
+ }
+
+ try {
+ template = engine.createTemplate(reader);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(e);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+
+ this.context = getContext();
+ }
+
+ @Benchmark
+ public String benchmark() {
+ Writer writer = new StringWriter();
+ Writable output = template.make(context);
+ try {
+ output.writeTo(writer);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ return writer.toString();
+ }
+
+}
diff --git a/src/main/java/com/mitchellbosecke/benchmark/Mustache_Samskivert.java b/src/main/java/com/mitchellbosecke/benchmark/Mustache_Samskivert.java
new file mode 100644
index 0000000..2c210f2
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/benchmark/Mustache_Samskivert.java
@@ -0,0 +1,116 @@
+package com.mitchellbosecke.benchmark;
+
+import java.io.*;
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.samskivert.mustache.*;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Setup;
+
+import com.mitchellbosecke.benchmark.model.Stock;
+
+public class Mustache_Samskivert extends BaseBenchmark {
+
+ private Template template;
+
+ @Setup
+ public void setup() {
+ InputStream is = this.getClass().getClassLoader()
+ .getResourceAsStream("templates/stocks.mustache.html");
+ Reader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(is, "UTF8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException(e);
+ }
+
+ template = com.samskivert.mustache.Mustache.compiler().withEscaper(Escapers.NONE).compile(reader);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Benchmark
+ public String benchmark() {
+
+ Map data = getContext();
+ data.put("items", new StockCollection((Collection) data.get("items")));
+
+ Writer writer = new StringWriter();
+ template.execute(data, writer);
+ return writer.toString();
+ }
+
+ /**
+ * This is a modified copy of
+ * {@link com.github.mustachejava.util.DecoratedCollection} - we need the
+ * first element at index 1.
+ *
+ * @param
+ */
+ private class StockCollection extends AbstractCollection {
+
+ private final Collection c;
+
+ public StockCollection(Collection c) {
+ this.c = c;
+ }
+
+ @Override
+ public Iterator iterator() {
+ final Iterator iterator = c.iterator();
+ return new Iterator() {
+
+ int index = 1;
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public StockView next() {
+ Stock next = iterator.next();
+ int current = index++;
+ return new StockView(current, current == 1, !iterator.hasNext(), next);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return c.size();
+ }
+ }
+
+ class StockView {
+
+ public final int index;
+
+ public final boolean first;
+
+ public final boolean last;
+
+ public final Stock value;
+
+ public final String negativeClass;
+
+ public final String rowClass;
+
+ public StockView(int index, boolean first, boolean last, Stock value) {
+ this.index = index;
+ this.first = first;
+ this.last = last;
+ this.value = value;
+ this.negativeClass = value.getChange() > 0 ? "" : "class=\"minus\"";
+ this.rowClass = index % 2 == 0 ? "even" : "odd";
+ }
+ }
+
+}
diff --git a/src/main/java/com/mitchellbosecke/benchmark/Mustache.java b/src/main/java/com/mitchellbosecke/benchmark/Mustache_Spullara.java
similarity index 98%
rename from src/main/java/com/mitchellbosecke/benchmark/Mustache.java
rename to src/main/java/com/mitchellbosecke/benchmark/Mustache_Spullara.java
index 69f8fe9..d3e87d1 100644
--- a/src/main/java/com/mitchellbosecke/benchmark/Mustache.java
+++ b/src/main/java/com/mitchellbosecke/benchmark/Mustache_Spullara.java
@@ -16,7 +16,7 @@
import com.github.mustachejava.MustacheFactory;
import com.mitchellbosecke.benchmark.model.Stock;
-public class Mustache extends BaseBenchmark {
+public class Mustache_Spullara extends BaseBenchmark {
private com.github.mustachejava.Mustache template;
diff --git a/src/main/java/com/mitchellbosecke/benchmark/Pebble.java b/src/main/java/com/mitchellbosecke/benchmark/Pebble.java
index cdf5d3a..f19ad06 100644
--- a/src/main/java/com/mitchellbosecke/benchmark/Pebble.java
+++ b/src/main/java/com/mitchellbosecke/benchmark/Pebble.java
@@ -20,8 +20,7 @@ public class Pebble extends BaseBenchmark {
@Setup
public void setup() throws PebbleException {
- PebbleEngine engine = new PebbleEngine();
- engine.getExtension(EscaperExtension.class).setAutoEscaping(false);
+ PebbleEngine engine = new PebbleEngine.Builder().extension(new EscaperExtension()).autoEscaping(false).build();
template = engine.getTemplate("templates/stocks.pebble.html");
this.context = getContext();
}
diff --git a/src/main/resources/templates/stocks.groovy.tpl b/src/main/resources/templates/stocks.groovy.tpl
new file mode 100644
index 0000000..2484011
--- /dev/null
+++ b/src/main/resources/templates/stocks.groovy.tpl
@@ -0,0 +1,76 @@
+yieldUnescaped ''
+html {
+ head {
+ title('Stock Prices')
+ meta('http-equiv':"Content-Type", content:"text/html; charset=UTF-8")
+ meta('http-equiv':"Content-Style-Type", 'content':"text/css")
+ meta('http-equiv':"Content-Script-Type", 'content':"text/javascript")
+ link('rel':"shortcut icon", 'href':"/images/favicon.ico")
+ link('rel':"stylesheet", 'type':"text/css", 'href':"/css/style.css", 'media':"all")
+ script('type':"text/javascript", 'src':"/js/util.js", "")
+ style('type':"text/css", """/**/""")
+ }
+ body {
+ h1('Stock Prices')
+
+ table {
+ thead {
+ tr {
+ th('#')
+ th('symbol')
+ th('name')
+ th('price')
+ th('change')
+ th('ratio')
+ }
+ }
+
+ tbody {
+ items.eachWithIndex {item, idx ->
+ tr (class: idx % 2 ? 'even' : 'odd'){
+ td(idx + 1)
+ td {
+ a(item.symbol, href: '/stocks/' + item.symbol)
+ }
+ td {
+ a(item.name, href: item.url)
+ }
+ td {
+ strong(item.price)
+ }
+ if (item.change < 0) {
+ td(item.change, class: 'minus')
+ td(item.ratio, class: 'minus')
+ } else {
+ td(item.change)
+ td(item.ratio)
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/templates/stocks.thymeleaf.html b/src/main/resources/templates/stocks.thymeleaf.html
index a1667f6..ebf45a9 100755
--- a/src/main/resources/templates/stocks.thymeleaf.html
+++ b/src/main/resources/templates/stocks.thymeleaf.html
@@ -55,7 +55,7 @@ Stock Prices
+ th:class="${itemStat.odd}? 'odd' : 'even'">
|
|
@@ -70,4 +70,4 @@ Stock Prices