Java: GraalVM Database Stream Performance
GraalVM is the new kid on the JVM block. It is an open-source Virtual Machine that is able to run many programming languages, such as Java, Rust and JavaScript, at the same time. GraalVM has also a new internal code optimizer pipeline that can improve performance significantly compared to other JVMs under some conditions. Learn how to reap the benefits of GraalVM and execute your code faster with no code modification.What is GraalVM?
Previous JVMs, such as Oracle JVM and OpenJDK JVM (both called “HotSpot”), has been around for a long time. They have evolved considerably over time and, over the course of the decades, we have seen performance rocketed compared to the Java 1.0 JVM. Significant JVM improvements include just-in-time compiling (JIT), C2 compiler, escape analysis etc. that all contributed to this positive development. But as with all technology, they will start to plateau at some point.GraalVM is a fresh start whereby a new internal architecture has been developed from the ground up. In particular, the JIT compiler, called Gaal, has been reworked. Unsurprisingly, the JIT compiler itself is written in Java, just like all the other GraalVM components. As it turns out, Graal is sometimes able to optimize your code better than some existing JVMs. In particular, some Stream types appear to benefit from running under Graal.
Database Stream Performance
There are a number of ways to write Java streams. The most obvious way is to use one of the built-in Java functionsStream::of
or Collection::stream
methods. These methods, however, requires that the elements in the Stream are present a-priori in the shape of Java objects. This means that the compiler cannot optimize them away under most conditions.I have therefore instead chosen to use the stream based ORM tool Speedment. This tool works with a technology that pulls in database content into an in-JVM-memory snapshot and creates Java streams directly from RAM. Thus, database tables are stored off-heap, thereby potentially avoiding the creation of Java Objects. Because Graal has an improved performance optimization pipeline, it is likely that it can better optimize away temporarily intermediary stream objects. In theory, Speedment and Graal would, therefore, be a perfect fit. I was therefore very eager to test how the already extreme performance of Speedement would be affected when running under GraalVM rather than running under HotSpot.
The following Speedment database streams were used to test performance. Read more on these streams and how they work in one of my previous article that you can find here.
private static final PredicateRATING_EQUALS_PG_13 = Film.RATING.equal(GeneratedFilm.Rating.PG13); private static final Comparator LENGTH_DESCENDING = Film.LENGTH.reversed(); @Benchmark public long filterAndCount() { return films.stream() .filter(RATING_EQUALS_PG_13) .count(); } @Benchmark public IntSummaryStatistics Complex() { return films.stream() .sorted(LENGTH_DESCENDING) .skip(745) .limit(5) .mapToInt(Film.RENTAL_DURATION.asInt()) .summaryStatistics(); }
The following JMH output was obtained for runs under GraalVM and HotSpot respectively:
Graal: Benchmark Mode Cnt Score Error Units Bench.Complex thrpt 5 8453285.715 ± 383634.200 ops/s Bench.filterAndCount thrpt 5 29755350.558 ± 674240.743 ops/s HotSpot: Benchmark Mode Cnt Score Error Units Bench.Complex thrpt 5 5334041.755 ± 176368.317 ops/s Bench.filterAndCount thrpt 5 20809826.960 ± 963757.357 ops/s
Being able to produce and consume over 30 million database streams per second with GraalVM/Speedment on a laptop with 4 CPU cores is quite astonishing. Imagine the performance on a server grade node with 24 or 32 CPU cores.
Here is how it looks in a chart (higher is better):
Ordinary Stream Performance
Initial tests show varying relative performance figures for built-in Java streams likeStream.of(“A”, “B”, “C”)
or List::stream
with various operations applied, for the different JVMs. I expect also these stream types to gain performance across the board once GraalVM has matured. Perhaps I will cover this in a future article.Setup
The following JMH setup was used for GraalVM and HotSpot:# Detecting actual CPU count: 8 detected # JMH version: 1.21 # VM version: JDK 1.8.0_172, GraalVM 1.0.0-rc6, 25.71-b01-internal-jvmci-0.48 # *** WARNING: JMH support for this VM is experimental. Be extra careful with the produced data. # VM invoker: /Applications/graalvm-ce-1.0.0-rc6/Contents/Home/jre/bin/java # VM options: -Dfile.encoding=UTF-8 # Warmup: 5 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 8 threads, will synchronize iterations # Benchmark mode: Throughput, ops/time # Detecting actual CPU count: 8 detected # JMH version: 1.21 # VM version: JDK 1.8.0_171, Java HotSpot(TM) 64-Bit Server VM, 25.171-b11 # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/bin/java # VM options: -Dfile.encoding=UTF-8 # Warmup: 5 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 8 threads, will synchronize iterations # Benchmark mode: Throughput, ops/time
The tests above were performed on a MacBook Pro (Retina, 15-inch, Mid 2015), 2.2 GHz Intel Core i7, 16 GB 1600 MHz DDR3 with 4 CPU cores and 8 threads. As can be seen in the logs, we should be careful to draw conclusions using JMH figures for Graal as the JMH support is experimental at this time.
Give it a Spin
Use the Speedment initializer to create a Speedment project template here.Download the latest version of GraalVM here.
The source code for the benchmarks is available here.
Feel free to reproduce the performance tests on another hardware platform and report the outcome in the comments below.
Conclusions
GraalVM seams to be a promising technology that can improve performance for certain Java stream types.GraalVM in combination with Speedment’s in-JVM-memory acceleration can enable significant stream performance for data analytic applications.