はじめに
ついにGraalVMの1.0 RCがリリースされました!
https://blogs.oracle.com/developers/announcing-graalvm
Java界隈の人はJava9のAOTやJava10のJITまわりの次世代基盤として、Rubyな人は爆速のRuby環境(Truffle Ruby)としてご存知なんじゃ無いでしょうか。
公式サイトには
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++.
https://www.graalvm.org/
と記載があり、あらゆる言語の統一VMを作りでバックツールやJITなどの資産活用をしようってのが目的のVM環境となります。
IBM/EclipseのORMと同じ思想を感じますね。
色々面白い事ができるのですが、その特徴の一つにJVM系言語をネイティブコンパイルできるという特徴的があります。
これにより、起動時のfootprintが悪いことで有名なJavaをかなり改善することができます。かつシングルパッケージ化もできます。
JavaをAOTコンパイルしてバイナリを作るというとGCJという懐かしのツールもありましたが、すでに最新のJavaには追従してないので、実質他には無い状態かと思います。
現在は、Community EditionとEnterprise Editionがあります。
とりあえず、インストールせずにさくりと試せるようにCE版のDocker環境を作ってみたので今回はそちらを使います。
https://hub.docker.com/r/koduki/docker-graalvm/
ネイティブモジュールにビルド
シンプルなコードをLinuxネイティブなバイナリにビルドしてみます。ビルドするコードは下記のとおり。
public class HelloWorld {
public static void main(String args[]){
System.out.println("Hello, World");
}
}
下記コマンドでPullを兼ねてバージョン番号を確認。GraalVMになってる事がわかります。
$ docker run -it -v "`pwd`:/src" koduki/graalvm:1.0.0-rc1 java -version
openjdk version "1.8.0_161"
OpenJDK Runtime Environment (build 1.8.0_161-12)
GraalVM 1.0.0-rc1 (build 25.71-b01-internal-jvmci-0.42, mixed mode)
続いてふつうにJavaでコンパイルして実行。
$ docker run -it -v "`pwd`:/src" koduki/graalvm:1.0.0-rc1 javac HelloWorld.java
$ docker run -it -v "`pwd`:/src" koduki/graalvm:1.0.0-rc1 java Hello, World
当たり前ですがHello, World
と表示されましたね。
今度はこれをLinuxバイナリにコンパイルしてみます。
$ docker run -it -v "`pwd`:/src" koduki/graalvm:1.0.0-rc1 native-image HelloWorld
Build on Server(pid: 9, port: 26681)*
classlist: 4,335.15 ms
(cap): 1,257.90 ms
setup: 3,411.13 ms
(typeflow): 12,345.25 ms
(objects): 3,497.83 ms
(features): 89.48 ms
analysis: 16,130.07 ms
universe: 541.30 ms
(parse): 4,822.63 ms
(inline): 2,370.80 ms
(compile): 20,626.56 ms
compile: 28,372.55 ms
image: 1,507.64 ms
write: 489.70 ms
[total]: 54,945.94 ms
$ docker run -it -v "`pwd`:/src" koduki/graalvm:1.0.0-rc1 ./helloworld
Hello, World
helloworld
というバイナリが出来ました。実行結果も同様ですね。
では、ネイティブバイナリにということで起動のフットプリントの差を見てみましょう。
Docker越しだとオーバーヘッドが大きいので、dockerに入って確認します。
$ docker run -it -v "`pwd`:/src" koduki/graalvm:1.0.0-rc1 /bin/bash
[root@d16c0ed04d03 src]# time java HelloWorld
Hello, World
real 0m0.167s
user 0m0.070s
sys 0m0.070s
[root@d16c0ed04d03 src]# time ./helloworld
Hello, World
real 0m0.031s
user 0m0.000s
sys 0m0.000s
バイナリ版の方が速く動いてる事がわかりますね。作りが単純なのでこれは実行速度というよりはVM起動等のオーバーヘッドの差と考えて良いと思います。
まとめ
とりあえずGraalVMのnative-imageを使ってJavaをバイナリにビルドしてみました。
記事には書いてませんが、複数のクラスでもちゃんと一つのバイナリにしてくれるのでfat-jarの代用としても活用できます。
@sonodar さんが「GraalVMを試してみた」の記事で試されているように動的なモジュール読み込みを伴なうものでは動作しないようなので、SpringBootとかを使ってるWebアプリをfat-jarでは無くシングルバイナリで動かすのは厳しそうですけど。現時点では。
ただ、フットプリントが小さくなったのでScalaをはじめJVM系言語でCLIツールを作り易くなるのは個人的には結構メリットですね。
スクリプトでも良いのですが、Javaで書いた方がメンテナーが集めやすかったりする都合もあるので。いくつかのライブラリと組合わせた挙動を確認していきたいです。
あと、この記事ではCE版なのでLinuxで試していますがEE版でMacのビルドも特に問題なく出来ました。
これでWindowsまでカバー出来るようになるとまた違った事が色々出来そうですね。
それではHappy Hacking!