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:

BenchmarkScoreScore errorDelta (%)
RawBeanList1596385,87610125,458Ref.
RojoBeanList196699,9174157,326-87,68%
RawFindNumber4109786,38833810,96Ref.
RojoFindNumber2984622,50117852,991-27,38%
RawList2392018,90819743,639Ref.
RojoList2015646,86311297,794-15,73%
RawMap1479750,2996464,942Ref.
RojoMap1347884,97811845,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 🙂 .