First impressions about Graal VM

微信扫一扫,分享到朋友圈

First impressions about Graal VM

Apr 29, 2018
/
GRAALVM ,
PERFORMANCE ,
POLYGLOT

My first impressions about Graal VM

Last week was the release of Oracle’s GraalVM
. As stated on the website:

High-performance polyglot VM

GraalVM is a universal virtual machine for running applications written in JavaScript, Python 3, Ruby, R, JVM-based languages like Java, Scala, Kotlin, and LLVM-based languages such as C and C++.

GraalVM removes the isolation between programming languages and enables interoperability in a shared runtime. It can run either standalone or in the context of OpenJDK, Node.js, Oracle Database, or MySQL.

There are several factors that could make one want to switch from a regular JRE to Graal VM:

  1. One of them could be the improved performance that it claims
  2. Another one could be the polyglot feature, to transparently mix and match supported languages
  3. The final one is a blend of the former: with native support, one could ship Java apps as native code

As a patented geek, I wanted to have first look real quick. Here are my first impressions.

Enterprise Edition or not?

The first step is to download
Graal VM itself. It comes into two flavors:

Community Edition
  • All open-source license
  • Free for production use
Enterprise Edition
  • Free for evaluation and other non-production uses
  • For commercial use and support options, the sales team should be contacted

First surprise: the CE
edition is only available for Linux operating systems. For OSX, one should get the EE
version.

There’s no edition for Windows (yet?)

Graal VM structure

The structure is similar to the one of traditional pre-9 Java JDK
.

Hence, GraalVM can be a drop-in replacement for any standard JDK.

Running java -version
returns the following output:

java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
GraalVM 1.0.0-rc1 (build 25.71-b01-internal-jvmci-0.42, mixed mode)
As of now, GraalVM is limited to Java 8 capabilities

Some performance benchmarks

The next step was to check whether there was an improvement in performance. I used the JMH
framework: it’s dedicated to that.

I used the following code:

public class MyBenchmark {

    @Benchmark
    public void testMethod() {
        List randomStrings = Stream.generate(() -> RandomStringUtils
                .randomAlphabetic(25))
                .limit(100_000)
                .collect(Collectors.toList());
        randomStrings.sort(String::compareToIgnoreCase);
    }
}

It was tested on 3 different JREs using the java -jar target/benchmarks.jar
command-line.

GraalVM

# JMH version: 1.20
# VM version: JDK 1.8.0_161, VM 25.71-b01-internal-jvmci-0.42
# VM invoker: /usr/local/graalvm-1.0.0-rc1/Contents/Home/jre/bin/java
# VM options: 
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: ch.frankel.blog.MyBenchmark.testMethod

Result "ch.frankel.blog.MyBenchmark.testMethod":
  6.795 ±(99.9%) 0.016 ops/s [Average]
  (min, avg, max) = (6.477, 6.795, 6.967), stdev = 0.068
  CI (99.9%): [6.778, 6.811] (assumes normal distribution)


# Run complete. Total time: 00:06:59

Benchmark                Mode  Cnt  Score   Error  Units
MyBenchmark.testMethod  thrpt  200  6.795 ± 0.016  ops/s

Oracle JDK 8

# JMH version: 1.20
# VM version: JDK 1.8.0_92, VM 25.92-b14
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/bin/java
# VM options: 
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: ch.frankel.blog.MyBenchmark.testMethod

Result "ch.frankel.blog.MyBenchmark.testMethod":
  6.727 ±(99.9%) 0.017 ops/s [Average]
  (min, avg, max) = (6.466, 6.727, 6.899), stdev = 0.070
  CI (99.9%): [6.710, 6.743] (assumes normal distribution)


# Run complete. Total time: 00:07:00

Benchmark                Mode  Cnt  Score   Error  Units
MyBenchmark.testMethod  thrpt  200  6.727 ± 0.017  ops/s

Oracle JDK 9

# JMH version: 1.20
# VM version: JDK 9.0.4, VM 9.0.4+11
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home/bin/java
# VM options: 
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: ch.frankel.blog.MyBenchmark.testMethod

Result "ch.frankel.blog.MyBenchmark.testMethod":
  7,136 ±(99.9%) 0,026 ops/s [Average]
  (min, avg, max) = (6,464, 7,136, 7,443), stdev = 0,111
  CI (99.9%): [7,110, 7,162] (assumes normal distribution)


# Run complete. Total time: 00:07:26

Benchmark                Mode  Cnt  Score   Error  Units
MyBenchmark.testMethod  thrpt  200  7,136 ± 0,026  ops/s

Here’s the sum-up:

GraalVM Oracle JDK 8 Oracle JDK 9

Average ops/s

6.795 ±(99.9%) 0.016

6.727 ±(99.9%) 0.017

7,136 ±(99.9%) 0,026

Min

6.477

6.466

6,464

Max

6.967

6.899

7,443

Std dev

0.068

0.070

0,111

CI (99.9%) (assumes normal distribution)

[6.778, 6.811]

[6.710, 6.743]

[7,110, 7,162]

Numbers speak for themselves: performance-wise, the gap between Graal VM and Java 8 is not significant. There’s one between them and Java 9 though. Also, Java 9 has the highest standard deviation.

Going native

GraalVM is able to turn a JAR into a native executable, via the native-image
command-line. I tried to do it with the created JAR.

native-image -H:+JNI -jar target/benchmarks.jar

However, when trying to run the newly created binary, I stumbled upon the following:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at java.lang.Throwable.(Throwable.java:310)
	at java.lang.Exception.(Exception.java:102)
	at java.lang.ReflectiveOperationException.(ReflectiveOperationException.java:89)
	at java.lang.reflect.InvocationTargetException.(InvocationTargetException.java:72)
	at com.oracle.svm.reflect.proxies.Proxy_3_Main_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;
	.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Caused by: java.lang.IllegalArgumentException: class org.openjdk.jmh.runner.options.TimeValue is not a value type
	at java.lang.Throwable.(Throwable.java:265)
	at java.lang.Exception.(Exception.java:66)
	at java.lang.RuntimeException.(RuntimeException.java:62)
	at java.lang.IllegalArgumentException.(IllegalArgumentException.java:52)
	at joptsimple.internal.Reflection.findConverter(Reflection.java:66)
	at joptsimple.ArgumentAcceptingOptionSpec.ofType(ArgumentAcceptingOptionSpec.java:106)
	at org.openjdk.jmh.runner.options.CommandLineOptions.(CommandLineOptions.java:109)
	at org.openjdk.jmh.Main.main(Main.java:41)
	... 4 more

Worse, trying to create an image for the Spring Pet Clinic
fails with the following:

error: unsupported features in 3 methods
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException:
  Unsupported constructor java.lang.ClassLoader.(ClassLoader) is reachable:
    The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime.
The unsupported element is then reported at run time when it is accessed the first time.
Trace:
	at parsing java.security.SecureClassLoader.(SecureClassLoader.java:76)
Call path from entry point to java.security.SecureClassLoader.(ClassLoader):
	at java.security.SecureClassLoader.(SecureClassLoader.java:76)
	at java.net.URLClassLoader.(URLClassLoader.java:100)
	at org.springframework.boot.loader.LaunchedURLClassLoader.(LaunchedURLClassLoader.java:50)
	at org.springframework.boot.loader.Launcher.createClassLoader(Launcher.java:74)
	at org.springframework.boot.loader.Launcher.createClassLoader(Launcher.java:64)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:49)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;
	.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException:
  Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime.
  The unsupported element is then reported at run time when it is accessed the first time.
Trace:
	at parsing java.net.URL.setURLStreamHandlerFactory(URL.java:1118)
Call path from entry point to java.net.URL.setURLStreamHandlerFactory(URLStreamHandlerFactory):
	at java.net.URL.setURLStreamHandlerFactory(URL.java:1110)
	at org.springframework.boot.loader.jar.JarFile.resetCachedUrlHandlers(JarFile.java:383)
	at org.springframework.boot.loader.jar.JarFile.registerUrlProtocolHandler(JarFile.java:373)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:48)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;
	.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException:
  Unsupported method java.security.ProtectionDomain.getCodeSource() is reachable:
    The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime.
  The unsupported element is then reported at run time when it is accessed the first time.
Trace:
	at parsing org.springframework.boot.loader.Launcher.createArchive(Launcher.java:118)
Call path from entry point to org.springframework.boot.loader.Launcher.createArchive():
	at org.springframework.boot.loader.Launcher.createArchive(Launcher.java:117)
	at org.springframework.boot.loader.ExecutableArchiveLauncher.(ExecutableArchiveLauncher.java:38)
	at org.springframework.boot.loader.JarLauncher.(JarLauncher.java:35)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;
	.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: Processing image build request failed

Dynamic class-loading just doesn’t work with GraalVM.

I tried with the H2 and HSQLDB standalone JARs…​ to no avail. In both cases, I got the following stack:

java.lang.NullPointerException
	at com.oracle.graal.pointsto.ObjectScanner.scanField(ObjectScanner.java:113)
	at com.oracle.graal.pointsto.ObjectScanner.doScan(ObjectScanner.java:263)
	at com.oracle.graal.pointsto.ObjectScanner.finish(ObjectScanner.java:307)
	at com.oracle.graal.pointsto.ObjectScanner.scanBootImageHeapRoots(ObjectScanner.java:78)
	at com.oracle.graal.pointsto.ObjectScanner.scanBootImageHeapRoots(ObjectScanner.java:60)
	at com.oracle.graal.pointsto.BigBang.checkObjectGraph(BigBang.java:581)
	at com.oracle.graal.pointsto.BigBang.finish(BigBang.java:552)
	at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:653)
	at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:381)
	at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)----

Conclusion

GraalVM has many promising features, including polyglot, even though I’m not much interested in that one by now. However, from my limited tire-kicking, it still has room for improvement. Performance-wise, it’s outperformed by Java 9. Also, the native image creation is too limited in its current state to be useful. IMHO, it’s in a MVP
state at the moment. I’m eagerly waiting the next version, and hope they will improve on the above issues.

Percona Live 2018 Community Report

上一篇

Gboard’s custom GIFs have come to select Android devices in beta

下一篇

你也可能喜欢

First impressions about Graal VM

长按储存图像,分享给朋友