Introduce and use GraalVM from the perspective of a Scala programmer.
GraalVM?
GraalVM seems to be able (sometimes) to speed up Scala (Java) programs if you take the plunge.
If you read this area, it is written in a little more detail. https://www.graalvm.org/docs/why-graal/#for-java-programs
To summarize briefly, the features are
Three points are listed.
In this article, I will touch on 1 and 3. See Embed Languages with the Graal Polyglot API for 2.
The GraalVM version is CE-1.0.0-rc9.
Since it has been uploaded to Github, download it and unpack it. [Releases · oracle/graal](https://github.com/oracle/graal/releases
$ wget https://github.com/oracle/graal/releases/download/vm-1.0.0-rc9/graalvm-ce-1.0.0-rc9-macos-amd64.tar.gz
$ tar zxvf graalvm-ce-1.0.0-rc9-macos-amd64.tar.gz
$ export GRAALVM_HOME=$PWD/graalvm-ce-1.0.0-rc9
In addition, an environment variable called GRAALVM_HOME
is set.
It's very easy to use, just install and use instead of Java.
$ java -jar main.jar #Run with Oracle JDK
$ $GRAALVM_HOME/bin/java -jar main.jar #Run on GraalVM
Use sbt-jmh to verify the performance to see if this is really fast. The code for performance verification is counted from the character string by referring to the official GraalVM demos: Graal Performance Examples for Java. I tried to do it. Sample code is given on Github.
package net.petitviolet.example
import org.openjdk.jmh.annotations._
@State(Scope.Thread)
class ForBench {
private val sentence = "In 2017 I would like to run ALL languages in one VM."
private val answer = 7
@Benchmark
@BenchmarkMode(Array(Mode.Throughput))
def bench_upperCaseCount() = {
upperCaseCount(sentence)
}
private def upperCaseCount(args: String) = {
val sentence = String.join(" ", args)
require(sentence.filter(Character.isUpperCase).length == answer)
}
}
Run jmh on this code.
You can optionally pass -jvm
to the jmh: run
task, so try running the benchmark while switching.
First, Oracle JDK 1.8.0_192. The resulting output is decimated appropriately.
sbt:main> jmh:run -i 10 -wi 10 -f1 -t 1 -jvm /path/to/java/1.8/bin/java
[info] Running (fork) org.openjdk.jmh.Main -i 10 -wi 10 -f1 -t 1 -jvm /path/to/java/1.8/bin/java
[info] # JMH version: 1.21
[info] # VM version: JDK 1.8.0_192, Java HotSpot(TM) 64-Bit Server VM, 25.192-b12
[info] # VM invoker: /path/to/java/1.8/bin/java
[info] # Benchmark mode: Throughput, ops/time
[info] # Benchmark: net.petitviolet.example.ForBench.bench_upperCaseCount
[info] Benchmark Mode Cnt Score Error Units
[info] ForBench.bench_upperCaseCount thrpt 10 2548794.689 ± 212771.778 ops/s
Then run with GraalVM 1.0.0-rc9.
sbt:main> jmh:run -i 10 -wi 10 -f1 -t 1 -jvm /path/to/graalvm-ce-1.0.0-rc9/Contents/Home/bin/java
[info] Running (fork) org.openjdk.jmh.Main -i 10 -wi 10 -f1 -t 1 -jvm /path/to/graalvm-ce-1.0.0-rc9/Contents/Home/bin/java
[info] # JMH version: 1.21
[info] # VM version: JDK 1.8.0_192, GraalVM 1.0.0-rc9, 25.192-b12-jvmci-0.49
[info] # *** WARNING: JMH support for this VM is experimental. Be extra careful with the produced data.
[info] # Benchmark mode: Throughput, ops/time
[info] # Benchmark: net.petitviolet.example.ForBench.bench_upperCaseCount
[info] # Run progress: 0.00% complete, ETA 00:03:20
[info] Benchmark Mode Cnt Score Error Units
[info] ForBench.bench_upperCaseCount thrpt 10 2904523.828 ± 28572.650 ops/s
[success] Total time: 203 s, completed Nov 25, 2018 9:46:55 PM
Simply comparing the results, GraalVM was a bit faster.
result | OracleJDK | GraalVM |
---|---|---|
Throughput | 2548794.689 | 2904523.828 |
However, this time it's a simple code, so it's possible that GraalVM happened to be more advantageous, but at least there are cases where just using GraalVM like this makes it faster.
NativeImage
Then, instead of JIT compilation, AOT compilation and spitting out native code, that is, executable binaries. SubstrateVM is the technology that supports it.
Substrate VM is a framework that allows ahead-of-time (AOT) compilation of Java applications under closed-world assumption into executable images or shared objects (ELF-64 or 64-bit Mach-O).
There is Scala Native in Scala, but I won't mention it here.
First, make native-image
usable.
However, if you have already downloaded it, you can just put it in your PATH.
$ export PATH=$PATH:$GRAALVM_HOME/Contents/Home/bin
$ native-image --version
GraalVM Version 1.0.0-rc9
You can use it to convert Scala (Java) programs into native code.
Specifically, you can give fat-JAR to the argument of the native-image
command.
If you are using sbt, you can easily generate fat-JAR using sbt-assembly, so you can use it.
To execute it, type the following command.
$ native-image \
-jar main.jar \ # sbt-Fat spit out in assembly-Specify JAR
-H:IncludeResources=".*.xml|.*.conf" \
-H:+ReportUnsupportedElementsAtRuntime \
-H:Name=app \ #Output binary path
--verbose
For the argument of -jar
, specify the fat-JAR spit out by sbt-assembly.
You can specify the resource file to be included in the binary with a regular expression with -H: IncludeResources
.
This time I wanted to include logback.xml and application.conf, so I specified it.
By the way, if you use -cp
instead of -jar
, you can do it without fat-JAR, but it is recommended because it is less memorable.
See Image Generation Options as other commands are optional.
What makes me happy to be a native is that the startup is explosive. Java dramatically improves the heavy spin-up of the JVM anyway.
The source code that runs in this sample is [scala_graalvm_prac / Application.scala](https://github.com/petitviolet/scala_graalvm_prac/blob/adad5c144534d473e5b61de38ec5e86f6d809172/modules/main/src/main/scala/net/petitviolet/example/Application. I'm using the one in scala), and it's an application that just starts and exits a web application that uses http4s.
First, try running it as java for comparison.
$ /usr/bin/time java -jar main.jar TimeTest
2018-11-26 17:24:29.129 INFO [main][o.h.b.c.n.NIO1SocketServerGroup] - Service bound to address /0:0:0:0:0:0:0:0:8080
2018-11-26 17:24:29.135 INFO [main][o.h.s.b.BlazeBuilder] - _ _ _ _ _
2018-11-26 17:24:29.135 INFO [main][o.h.s.b.BlazeBuilder] - | |_| |_| |_ _ __| | | ___
2018-11-26 17:24:29.135 INFO [main][o.h.s.b.BlazeBuilder] - | ' \ _| _| '_ \_ _(_-<
2018-11-26 17:24:29.135 INFO [main][o.h.s.b.BlazeBuilder] - |_||_\__|\__| .__/ |_|/__/
2018-11-26 17:24:29.135 INFO [main][o.h.s.b.BlazeBuilder] - |_|
2018-11-26 17:24:29.230 INFO [main][o.h.s.b.BlazeBuilder] - http4s v0.18.9 on blaze v0.12.13 started at http://[0:0:0:0:0:0:0:0]:8080/
2018-11-26 17:24:29.230 INFO [main][n.p.e.Application] - start server.
2018-11-26 17:24:29.230 INFO [main][n.p.e.Application] - start shutting down immediately.
2018-11-26 17:24:29.235 INFO [main][o.h.b.c.ServerChannel] - Closing NIO1 channel /0:0:0:0:0:0:0:0:8080 at Mon Nov 26 17:24:29 JST 2018
2018-11-26 17:24:29.237 INFO [main][o.h.b.c.n.NIO1SocketServerGroup] - Closing NIO1SocketServerGroup
2018-11-26 17:24:29.237 INFO [main][n.p.e.Application] - shutting down completed.
1.74 real 1.84 user 0.20 sys
The result.
Next, try executing the binary created by native-image.
$ /usr/bin/time ./app TimeTest
2018-11-26 17:24:37.190 INFO [main][o.h.b.c.n.NIO1SocketServerGroup] - Service bound to address /0:0:0:0:0:0:0:0:8080
2018-11-26 17:24:37.190 INFO [main][o.h.s.b.BlazeBuilder] - _ _ _ _ _
2018-11-26 17:24:37.190 INFO [main][o.h.s.b.BlazeBuilder] - | |_| |_| |_ _ __| | | ___
2018-11-26 17:24:37.190 INFO [main][o.h.s.b.BlazeBuilder] - | ' \ _| _| '_ \_ _(_-<
2018-11-26 17:24:37.190 INFO [main][o.h.s.b.BlazeBuilder] - |_||_\__|\__| .__/ |_|/__/
2018-11-26 17:24:37.190 INFO [main][o.h.s.b.BlazeBuilder] - |_|
2018-11-26 17:24:37.190 INFO [main][o.h.s.b.BlazeBuilder] - http4s v0.18.9 on blaze v0.12.13 started at http://[0:0:0:0:0:0:0:0]:8080/
2018-11-26 17:24:37.190 INFO [main][n.p.e.Application] - start server.
2018-11-26 17:24:37.190 INFO [main][n.p.e.Application] - start shutting down immediately.
2018-11-26 17:24:37.190 INFO [main][o.h.b.c.ServerChannel] - Closing NIO1 channel /0:0:0:0:0:0:0:0:8080 at Mon Nov 26 17:24:37 JST 2018
2018-11-26 17:24:37.190 INFO [main][o.h.b.c.n.NIO1SocketServerGroup] - Closing NIO1SocketServerGroup
2018-11-26 17:24:37.190 INFO [main][n.p.e.Application] - shutting down completed.
0.03 real 0.01 user 0.01 sys
Almost nothing has changed in the output of the application, but since it is 1.74sec → 0.03sec, it can be seen that the startup is clearly faster.
It is said that Twitter has been put into production, but it may be possible to adopt it depending on the result of operation / performance verification. Another advantage is that it is easy to return even if there is a problem.
However, it is still too early to introduce native coding using native-image
.
The restrictions are described in LIMITATIONS.md. The big thing is that dynamic class loading isn't supported and some reflections aren't available. I often use macros in Scala, but it often doesn't work. Due to this, I couldn't use Play framework or Akka-HTTP. In addition to the web application framework, there are still many restrictions, such as trying to use Logback's Async Appender does not work. Since AOT is just ahead-of-time, it has become difficult to want to do it at runtime. By the way, there was a GraalVM demo that converted the Scala compiler into a native-image, so you may want to refer to it as well. graalvm-demos/scala-days-2018/scalac-native at master · graalvm/graalvm-demos
However, it doesn't mean that it can be used at all. For example, it may be an option to operate tools for CLI with Scala (Java) + native-image
instead of Golang.
If it can be used more universally, the incompatibility between the Docker / Kubernetes era and the JVM spin-up may be improved, so I'd like to expect it in the future.
Recommended Posts