Zrovna nám nedávno vyšlo Rojo ve verzi 1.0.3 a já jsem si říkal, jak je na tom tato knihovna s výkonem. Rozhodl jsem se ji tedy trochu otestovat a proměřit pomocí JMH a i přesto, že se mi způsob vytváření nového benchmarku přes maven archetyp příliš nepozdával, tak se mi to nakonec rozjet podařilo 🙂 .
Benchmark
Pro náš bechmark jsem si připravil pár metod s použitím Rojo a bez, které sice nepokrývají úplně všechny featury knihovny, ale pro zevrubný přehled, jakou daň programátor zaplatí za čistý kód a pohodlí, vystačí:
public class RojoBenchmark {
@Benchmark
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
public void testRojoFindNumber() {
String input = "abcd123abcd";
String number = Rojo.find("\\d+", input).get();
}
@Benchmark
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
public void testRojoList() {
String input = "abcd123abcd";
List<String> pairs = Rojo.asList(".{2}", input);
}
@Benchmark
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
public void testRojoMap() {
String input = "abcd123abcd";
Map<String, String> map = Rojo.asMap("(.)(.)", input);
}
@Benchmark
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
public void testRojoBeanList() {
String input = "abcd123abcd";
List<RojoBean> beanList = Rojo.of(RojoBean.class).matchList(input);
}
@Benchmark
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
public void testRawBeanList() {
String input = "abcd123abcd";
List<RojoBean> beanList = new ArrayList<>();
Matcher m = Pattern.compile("(.)(.)").matcher(input);
while (m.find()) {
RojoBean bean = new RojoBean();
bean.setFirst(m.group(1));
bean.setSecond(m.group(2));
beanList.add(bean);
}
}
@Benchmark
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
public void testRawMap() {
String input = "abcd123abcd";
HashMap<String, String> map = new HashMap<String, String>();
Matcher m = Pattern.compile("(.)(.)").matcher(input);
while (m.find()) {
map.put(m.group(1), m.group(2));
}
}
@Benchmark
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
public void testRawList() {
String input = "abcd123abcd";
List<String> pairs = new ArrayList<>();
Matcher m = Pattern.compile(".{2}").matcher(input);
while (m.find()) {
pairs.add(m.group());
}
}
@Benchmark
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
public void testRawFindNumber() {
String input = "abcd123abcd";
Matcher m = Pattern.compile("\\d+").matcher(input);
String number = null;
if (m.find()) {
number = m.group();
}
}
}
a ještě použitý bean:
@Regex("(.)(.)")
public class RojoBean {
@Group(1)
private String first;
@Group(2)
private String second;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
}
Výsledky
Trochu mě překvapilo, že při defaultním nastavení běží JMH tak strašně dlouho (i více něž 1h), tak jsem to musel trochu zoptimalizovat 🙂 a po cca 16 minutách běhu JMH vše zesumarizoval takto:
Result: 1347884,978 ±(99.9%) 11845,939 ops/s [Average] Statistics: (min, avg, max) = (1290629,764, 1347884,978, 1382010,209), stdev = 23929,383 Confidence interval (99.9%): [1336039,039, 1359730,918] # Run complete. Total time: 00:16:22 Benchmark Mode Samples Score Score error Units o.s.RojoBenchmark.testRawBeanList thrpt 50 1596385,876 10125,458 ops/s o.s.RojoBenchmark.testRojoBeanList thrpt 50 196699,917 4157,326 ops/s o.s.RojoBenchmark.testRawFindNumber thrpt 50 4109786,388 33810,960 ops/s o.s.RojoBenchmark.testRojoFindNumber thrpt 50 2984622,501 17852,991 ops/s o.s.RojoBenchmark.testRawList thrpt 50 2392018,908 19743,639 ops/s o.s.RojoBenchmark.testRojoList thrpt 50 2015646,863 11297,794 ops/s o.s.RojoBenchmark.testRawMap thrpt 50 1479750,299 6464,942 ops/s o.s.RojoBenchmark.testRojoMap thrpt 50 1347884,978 11845,939 ops/s
Pro lepší přehlednost jsem výsledky přepsal do této tabulky:
| Benchmark | Score | Score error | Delta (%) |
|---|---|---|---|
| RawBeanList | 1596385,876 | 10125,458 | Ref. |
| RojoBeanList | 196699,917 | 4157,326 | -87,68% |
| RawFindNumber | 4109786,388 | 33810,96 | Ref. |
| RojoFindNumber | 2984622,501 | 17852,991 | -27,38% |
| RawList | 2392018,908 | 19743,639 | Ref. |
| RojoList | 2015646,863 | 11297,794 | -15,73% |
| RawMap | 1479750,299 | 6464,942 | Ref. |
| RojoMap | 1347884,978 | 11845,939 | -8,91% |
Jak jde vidět, nejhůře je na tom samotná hlavní feature, kvůli které Rojo vzniklo, a sice mapování na POJO objekty, která je až o 87% pomalejší, než její boilerplatová alternativa. Nicméně dle mého názoru, to až tak zlé zpomalení není, když vezmu svou domnělou úvahu, že 98% (toto číslo je čistě jen má představa, bez jakéhokoliv reálného podkladu) aplikací nějakou extrémní optimalizaci pro rychlost nepotřebuje, neb (ať si říká kdo chce, co chce) Java prostě rychlá je 🙂 .