Už je to nějaký pátek, co jsem objevil knihovnu JavaSlang, která do Javy přináší trochu funkcionálního paradigmatu a dále taky pohodlnější způsob, jak pracovat se streamy. Říkal jsem si, jestli by byl dobrý nápad tuto knihovnu použít v některých našich projektech a tak jsem se rozhodl udělat benchmark pro verzi 2.0.0-RC4, jak je na tom s rychlostí oproti standardnímu Stream API v Javě 8.

Příprava

Pro každý běh jsem si připravil List se 100 000 čísly a každou metodu nechám proběhnout 1000x, aby měl JIT dostatek času (a motivace) na optimalizaci – snad to bude stačit :-).

public class JavaSlangBenchmarkTest {
    private int runCycles = 1000;
    private List<Integer> numbers;

    @Before
    public void setUp() throws Exception {
        numbers = IntStream.range(0, 100000)
                .boxed()
                .collect(Collectors.toList());
    }

Test JavaSlangu

    @Test
    public void testToListSlang() {
        for ( int i = 0; i < runCycles; i++) { 
            Stream.ofAll(numbers) 
                    .filter( n -> n % 2 == 0)
                    .map( n -> n*2)
                    .map(String::valueOf)
                    .toList();
        }
    }

Výsledek: 14,9s. Zajímavé však je, že pokud použiju .toJavaList(), je to o něco rychlejší: 11,7s.

Dále zkusme spojit List v řetězec:

    @Test
    public void testToStringSlang() {
        for ( int i = 0; i < runCycles; i++) { 
            Stream.ofAll(numbers) 
                    .filter( n -> n % 2 == 0)
                    .map( n -> n*2)
                    .map(String::valueOf)
                    .mkString(",");
        }
    }

Výsledek: 13,9s

A na závěr třeba grupování. Musel jsem to omezit pouze na 1000 prvků, ať to netrvá do nekonečna :-).

for ( int i = 0; i < runCycles; i++) {
    Stream.ofAll(numbers)
            .filter(n -> n % 2 == 0)
            .map(n -> n * 2)
            .map(String::valueOf)
            .take(1000)
            .toList()
            .groupBy( s -> s.length());
}

Výsledek: 15,4s.

Test java.util.stream

A nyní to samé s použitím klasického Stream API:

@Test
public void testToListStandard() {
    for ( int i = 0; i < runCycles; i++) {
        numbers.stream()
                .filter( n -> n % 2 == 0)
                .map( n -> n*2)
                .map(String::valueOf)
                .collect(Collectors.toList());
    }
}

Výsledek: 2,9s.

@Test
public void testToStringStandard() {
    for ( int i = 0; i < runCycles; i++) {
        numbers.stream()
                .filter( n -> n % 2 == 0)
                .map( n -> n*2)
                .map(String::valueOf)
                .collect(Collectors.joining(","));
    }
}

Výsledek: 4s.

@Test
public void testGroupByStandard() {
    for ( int i = 0; i < runCycles; i++) {
        numbers.stream()
                .filter(n -> n % 2 == 0)
                .map(n -> n * 2)
                .map(String::valueOf)
                .limit(1000)
                .collect(Collectors.groupingBy(s -> s.length()));
    }
}

Výsledek: 0,225s.

Závěr

JavaSlang nám sice přináší spoustu zajímavých metod (podobně jako kolekce v jazyce Scala), ale je zde třeba počítat s podstatným výkonnostním úbytkem.

TestJavaSlangjava.util.stream
toList14,9s / 11,7s2,9s
toString13,9s4s
groupBy15,4s0,225s