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 🙂 .