sbt-native-imageで実行可能なバイナリを作成する

graalvmをつかって実行可能なバイナリを作成できるsbt プラグイン sbt-native-image を試してみました。
2020.09.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

graalvmをつかって実行可能なバイナリを作成できるsbt プラグイン sbt-native-image を試してみました。

ネイティブ化したプログラム

今回ネイティブ化したコードは以下のフィボナッチ数を求めるプログラムです。

package example

import zio._
import zio.console._

object Fib extends zio.App {

  def fib[R](n: Long):ZIO[R, Nothing, Long] = {
    if (n == 0 || n == 1) ZIO.succeed(n)
    else for {
      a <- ZIO.unit *> fib(n-1)
      b <- ZIO.unit *> fib(n-2)
    } yield a + b
  }

  def run(args: List[String]): URIO[ZEnv, ExitCode] =
    (for {
      n <- ZIO.fromOption(args.headOption.flatMap(_.toLongOption))
      ans <- fib(n)
      _ <- putStrLn(ans.toString)
    } yield ()).exitCode

}

build.sbtは以下の通り

name := "sbt-native-image-example"

version := "0.1"

scalaVersion := "2.13.3"

libraryDependencies += "dev.zio" %% "zio" % "1.0.1"

インストール(project/plugins.sbt)]

例によってプラグインを下記のように指定します。

addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.2.1")

ビルド

ネイティブイメージを作成するにはnativeImageタスクでビルドします。初回はgraalvmのダウンロードをするため10分以上かかります。

macOS上でビルドしていた場合sayコマンドの音声で「Native image ready!」と知らせてくれます。

実行ログ(1回目)

~/s/g/c/b/sbt-native-image-example ❯❯❯  sbt
[info] welcome to sbt 1.3.13 (Amazon.com Inc. Java 1.8.0_242)
[info] loading settings for project global-plugins from idea.sbt,plugins.sbt ...
[info] loading global plugins from /Users/sasaki.kazuhiro/.sbt/1.0/plugins
[info] loading settings for project sbt-native-image-example-build from plugins.sbt ...
[info] loading project definition from /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/project
[info] loading settings for project sbt-native-image-example from build.sbt ...
[info] set current project to sbt-native-image-example (in build file:/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/)
[info] sbt server started at local:///Users/sasaki.kazuhiro/.sbt/1.0/server/f15941dddccd2edc712e/sock
sbt:sbt-native-image-example> nativeImage
[info] Compiling 1 Scala source to /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/scala-2.13/classes ...
Downloading https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz
Still downloading:
https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz (59.04 %, 253120899 / 428722016)

Still downloading:
https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz (99.40 %, 426168633 / 428722016)

Downloaded https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz
Extracting
  /Users/sasaki.kazuhiro/Library/Caches/Coursier/v1/https/github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-darwin-amd64-20.1.0.tar.gz
in
  /Users/sasaki.kazuhiro/Library/Caches/Coursier/jvm/graalvm-java11@20.1.0
Done
Downloading: Component catalog from www.graalvm.org
Processing Component: Native Image
Downloading: Component native-image: Native Image  from github.com
Installing new component: Native Image (org.graalvm.native-image, version 20.1.0)
[info] /Users/sasaki.kazuhiro/Library/Caches/Coursier/jvm/graalvm-java11@20.1.0/Contents/Home/bin/native-image -cp /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image-internal/manifest.jar example.Fib /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example
Build on Server(pid: 10427, port: 60389)*
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]    classlist:   2,600.36 ms,  0.96 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]        (cap):   4,170.49 ms,  0.96 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]        setup:   5,685.32 ms,  0.96 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]     (clinit):     590.75 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]   (typeflow):   8,507.30 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]    (objects):   9,635.14 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]   (features):     405.95 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]     analysis:  19,657.76 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]     universe:     461.65 ms,  1.72 GB
Warning: Reflection method java.lang.Class.getDeclaredMethod invoked at zio.internal.stacktracer.impl.AkkaLineNumbers$.getStreamForLambda(AkkaLineNumbers.scala:201)
Warning: Aborting stand-alone image build due to reflection use without configuration.
Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Build on Server(pid: 10427, port: 60389)
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]    classlist:      92.97 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]        (cap):   2,152.91 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]        setup:   2,455.07 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]     (clinit):     151.36 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]   (typeflow):   2,721.18 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]    (objects):   3,078.55 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]   (features):      94.06 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]     analysis:   6,192.40 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]     universe:     211.55 ms,  2.21 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]      (parse):     726.45 ms,  2.29 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]     (inline):   1,246.53 ms,  2.29 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]    (compile):   6,047.58 ms,  3.22 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]      compile:   8,400.81 ms,  3.22 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]        image:     873.75 ms,  3.22 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]        write:     315.83 ms,  3.22 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:10427]      [total]:  18,610.73 ms,  3.22 GB
Warning: Image '/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary).
[info] Native image ready!
[info] /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example
[success] Total time: 683 s (11:23)

オプションの指定

上記の実行結果ログを見ると以下のようにJDKをつかったイメージが作成されているのが分かります。メッセージで示されているように -no-fallback を指定して再実行してみます。

  • Warning: Image '/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary).

オプションはnativeImageOptionsに指定します。

nativeImageOptions ++= List("--no-fallback")

実行ログ(2回目)

2回目の実行結果は以下の通りです。

sbt:sbt-native-image-example> nativeImage
[info] Compiling 1 Scala source to /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/scala-2.13/classes ...
[info] /Users/sasaki.kazuhiro/Library/Caches/Coursier/jvm/graalvm-java11@20.1.0/Contents/Home/bin/native-image -cp /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image-internal/manifest.jar --no-fallback example.Fib /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example
Build on Server(pid: 29968, port: 56106)*
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]    classlist:   2,565.24 ms,  0.96 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]        (cap):   4,323.05 ms,  0.96 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]        setup:   5,702.13 ms,  0.96 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]     (clinit):     547.54 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]   (typeflow):   8,567.28 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]    (objects):   9,627.95 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]   (features):     417.20 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]     analysis:  19,631.94 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]     universe:     453.48 ms,  1.72 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]      (parse):   1,295.92 ms,  2.30 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]     (inline):   1,597.05 ms,  2.30 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]    (compile):   9,656.70 ms,  3.29 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]      compile:  13,209.95 ms,  3.29 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]        image:   1,394.82 ms,  3.29 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]        write:     808.47 ms,  4.73 GB
[/Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example:29968]      [total]:  44,011.19 ms,  4.73 GB
[info] Native image ready!
[info] /Users/sasaki.kazuhiro/src/github.com/cm-kazup0n/blog-examples/sbt-native-image-example/target/native-image/sbt-native-image-example
[success] Total time: 53 s

起動時間の比較

作成したバイナリ実行時間をsbt-assemblyで作成したFatJarをJVMで実行した結果と比較してみます。 起動時間がかなり短縮されているのが分かります。

~/s/g/c/b/sbt-native-image-example ❯❯❯  java -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment Corretto-8.242.08.1 (build 1.8.0_242-b08)
OpenJDK 64-Bit Server VM Corretto-8.242.08.1 (build 25.242-b08, mixed mode)
~/s/g/c/b/sbt-native-image-example ❯❯❯  time ./target/native-image/sbt-native-image-example 5
5
        0.01 real         0.00 user         0.00 sys
~/s/g/c/b/sbt-native-image-example ❯❯❯  time java -jar target/scala-2.13/sbt-native-image-example-assembly-0.1.jar 5
5
        0.83 real         1.46 user         0.20 sys

まとめ

軽く触ってみましたがプラグインの導入だけで(graalvmのインストールをしなくても)バイナリの作成が行えるのは手軽でいいのかなと思いました。