MicronautでGraalVM native imageを動かす

Introduction

前回はMicronautフレームワークについて紹介しました。
MicronautはGraalVMで動作するアプリも簡単にできるので、
今回はそれを試してみます。

Using GraalVM with Micronaut

About GraalVM

GraalVMとは、最近注目されている多言語対応の仮想マシンとプラットフォームです。
現在はJavaやJVM言語(ScalaとかClojureとか)の実行機能に加え、
node.js(JavaScript)/Ruby/Python/R言語/LLVMベースの言語をサポートしています。
これにより、複数のプログラミング言語が混在したプログラムを実行することも可能になります。

また、GraalVMではJavaプログラムをネイティブコンパイルすることができます。
こうすることで高速起動&フットプリントが小さいネイティブプログラムを作成することが可能です。

Words related to GraalVM

  • Graal : JavaのJITコンパイラ
  • GraalVM : Graalを中心とした多言語対応仮想マシンとプラットフォーム
  • SubstrateVM : 埋め込み用途に適した軽量VM
  • Truffle : インタプリタ構築用ツールおよびAPI

SubstrateVMについてはここ
Truffleについてはここを参照。

GraalはJavaのJITコンパイラ、
GraalVMはJITコンパイラにGraalを使っている仮想マシンとプラットフォームを指します。

Advantages of GraalVM

GraalVMを使ってうれしいことの1つは、複数言語を1つのランタイムで動かせることです。
これによってシステムの構成を言語ごとに変更する必要がなくなります。

そしてもう1つ、アプリの起動がとても高速になるというメリットがあります。
GraalVMではAOTコンパイラをつかってnative imageを作成することができるので。
これにより起動が高速化します。
※AOTコンパイラで事前にVM上で動作する言語にコンパイルされるため起動が速くなる

例えばAWS Lambdaなどのserverless環境でGraalVMを使用する場合、
実行コンテキスト起動時ロードの高速化が可能になります。

Develop sample application

ではMicronautでGraalVMのnative imageを動かしてみましょう。
まずはSDKMANを使ってGraalVMをインストールします。

また、前回を参考にMicronautもインストールしましょう。
※先日、Micronaut 1.2がリリースされました

% sdk install java 19.1.1-grl
% sdk use java 19.1.1-grl
Using java version 19.1.1-grl in this shell.

javaコマンドで使っているJVMを確認。

% java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-20190711112007.graal.jdk8u-src-tar-gz-b08)
OpenJDK 64-Bit GraalVM CE 19.1.1 (build 25.222-b08-jvmci-19.1-b01, mixed mode)

準備ができたらmnコマンドで、graal-native-image featureを指定してアプリの雛形を作成します。

% mn create-app hello-graal --features graal-native-image

適当なコントローラクラスを作成。

package hello.graal;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;

//src/main/java/hello/graal/HelloController.java
@Controller("/hello")
public class HelloController {
    @Get(produces = MediaType.TEXT_PLAIN)
    public String index() {
        return "Hello Micronaut from GraalVM!";
    }
}

まずは普通にrunしてみます。

% cd path/your/graal-app
% ./gradlew run

> Task :run
22:12:59.185 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 7713ms. Server Running: http://localhost:8080

起動に7713ミリ秒かかりました。

では次にnative imageを作成して実行してみましょう。
native-imageコマンドを利用するため、gu installでnative-imageをインストールします。

% cd path/rour/graal-app
gu install native-image

gradle assembleでnative imageを作ります。
image作成はけっこう時間かかります。

$ ./gradlew assemble
hello-graal native-image --no-server -cp build/libs/hello-graal-0.1-all.jar
[hello-graal:54971]    classlist:  10,659.98 ms
[hello-graal:54971]        (cap):   4,312.39 ms
[hello-graal:54971]        setup:  12,598.84 ms
[hello-graal:54971]   (typeflow): 111,565.10 ms
[hello-graal:54971]    (objects):  43,374.50 ms
[hello-graal:54971]   (features):   8,006.12 ms
[hello-graal:54971]     analysis: 184,597.60 ms
[hello-graal:54971]     (clinit):   2,869.04 ms
[hello-graal:54971]     universe:  13,006.53 ms
[hello-graal:54971]      (parse):  14,518.54 ms
[hello-graal:54971]     (inline):  36,025.61 ms
[hello-graal:54971]    (compile):  96,351.35 ms
[hello-graal:54971]      compile: 170,146.60 ms
[hello-graal:54971]        image:  10,961.05 ms
[hello-graal:54971]        write:   3,159.46 ms
[hello-graal:54971]      [total]: 441,090.72 ms

生成したnative imageを実行してみます。

% ./hello-graal
22:12:01.299 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 137ms. 
Server Running: http://localhost:8080

普通に起動したとき7713ミリ秒かかっていた時間が、
native imageだと137ミリ秒と非常に高速で起動しています。

Conclusion

今回はMicronautでGraalVMのnative imageを作成して実行してみました。
とても起動が速くなり、AWS Lambdaではうってつけといわれるのも理解できます。

次はAWS Lambda上でMicronaut + GraalVMをやってみたいと思います。