Run Scala with GraalVM & make it a native image

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

  1. Accelerate Java There are some improvements to the JIT compilation.
  2. Run another language (JS or Python) within the Java context Polyglot application can be developed
  3. Create a Native image AOT compilation instead of JIT

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.

Installation

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.

Accelerate your programs with GraalVM

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 happens if you convert with NativeImage

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.

Other miscellaneous feelings

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

Run Scala with GraalVM & make it a native image
Try building Java into a native module with GraalVM
[Java] Make it a constant
Run lambda with custom docker image
Make a digging maze with Ruby2D
[Java, Scala] Image resizing with ImageIO
Make a slideshow tool with JavaFX
Make a Christmas tree with swift
Priority Queue max Make it a queue
Make a garbage reminder with line-bot-sdk-java
Make a list map with LazyMap
Make JupyterLab run anywhere with docker
Run (provisionally) a Docker image with ShellCommandActivity on AWS Data Pipeline
Make a typing game with ruby
Create a program to post to Slack with GO and make it a container
Let's make a Christmas card with Processing!
Make a family todo list with Sinatra
Make a family todo list with Sinatra
Let's make a smart home with Ruby!
Run a DMN with the Camunda DMN Engine
Make a login function with Rails anyway
[docker] [nginx] Make a simple ALB with nginx
Make a site template easily with Rails
Created a native extension of Ruby with Rust and published it as a gem
Run Rust from Java with JNA (Java Native Access)
Run Scala applications with Spring Boot through Gradle
Create Scala Seq from Java, make Scala Seq a Java List
Let's make a search function with Rails (ransack)
Make System.out a Mock with Spock Test Framework