GraalVMはどれだけ遅いか

GraalVM流行ってますね。
そして、多くの人はGraalをAOTとして使うnative-imageのことだけをGraalVMと言ってたりします。
ご安心を。このエントリではGraalをJITとして使うHotSpotモードとGraalをAOTとして使うnative-imageの両方が遅いという話です。

GraalVMは速い、と言われてますが、残念ながらHotSpotモードでC2より速い結果を手元では出せていません。
公式ブログでは1.7倍から5倍速くなると書いてますけど、手元では再現できてません。
Under the hood of GraalVM JIT optimizations - graalvm - Medium

native-imageは速い、というのはよくありますが、これはネイティブ化によりJVMの起動時間や最適化の時間、最適化されずに動く時間が省略されるので起動が速い、という話です。長く動くプロセスの場合、そういった起動にかかる時間というのは無視できるようになり、実行時に集めた情報を使って最適化するJITのほうが速いです。

用語について

ところでここで用語の確認を。
GraalVMというのは、Javaで書かれたJITコンパイラGraalを中心とした多言語環境です。
普通にGraalをJavaJITコンパイラとして使うのがGraalVMのHotSpotモードです。ようするにjavaコマンドです。
Graalの最適化機構を一般のスクリプト言語から使えるようにしてJavaScriptRubyJITコンパイラにしてしまうのがTruffleです。
そして、Graalを事前にJavaコードに適用してネイティブ化するというのがnative-imageです。

計測

ということで、どのくらい遅いか比べてみます。
コードはこのレイトレ。
https://github.com/kishida/smallpt4j/blob/original/src/main/java/naoki/smallpt/SmallPT.java
commonsのFastMathを使っているので、通常のjava.lang.Mathに置き換えて実行します。また、ループ中のSystem.out.printlnはコメントアウトしました。
それはそうと、以前はこのコードはImageIOを使っているのでGraalVMでネイティブ化できなかったのですが、いまではできるようになってます。
実行するとこんな感じのPNG画像が出力されます。
f:id:nowokay:20190627042056p:plain

GraalVM 19.0.2 CE

ここではWindowsで動かしてみました。GraalVMは19.0.2CEです。
まずはHotSpotモード。

C:\Users\naoki\Documents\prj>java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-20190603180034.buildslave.jdk8u-src-tar--b03)
OpenJDK 64-Bit GraalVM CE 19.0.2 (build 25.212-b03-jvmci-19-b04, mixed mode)

C:\Users\naoki\Documents\prj>java SmallPT
Samples:40 Type:master Time:PT9.713S

10秒弱。

ネイティブ化してみます。

C:\Users\naoki\Documents\prj>native-image SmallPT
[smallpt:21796]    classlist:   1,793.30 ms
...
[smallpt:21796]        write:     786.36 ms
[smallpt:21796]      [total]:  26,307.39 ms

C:\Users\naoki\Documents\prj>smallpt
Samples:40 Type:master Time:PT45.234S

45秒。だいぶ遅い!

GraalVM 19.0.2 EE

ついでにEEでも試してみます。

C:\Users\naoki\Documents\prj>java -version
java version "1.8.0_212"
Java(TM) SE Runtime Environment (build 1.8.0_212-b31)
Java HotSpot(TM) 64-Bit GraalVM EE 19.0.2 (build 25.212-b31-jvmci-19-b04, mixed mode)

C:\Users\naoki\Documents\prj>java SmallPT
Samples:40 Type:master Time:PT10.281S

10秒強。あんま変わらん。

ではネイティブ化。

C:\Users\naoki\Documents\prj>native-image SmallPT
[smallpt:43056]    classlist:   2,165.31 ms
...
[smallpt:43056]        image:     788.17 ms
Warning: Generating and stripping of debug info not supported on Windows[smallpt:43056]        write:     775.12 ms
[smallpt:43056]      [total]:  29,450.40 ms

C:\Users\naoki\Documents\prj>smallpt
Samples:40 Type:master Time:PT13.039S

13秒!CEに比べるとめっちゃ速い!

OpenJDK

それではC2版。つまりふつうのOpenJDK

$ java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_212-b03)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.212-b03, mixed mode)

$ java SmallPT
Samples:40 Type:master Time:PT6.795S

6.8秒。GraalVMに比べるとだいぶ速いです。

OpenJDKの11でも試してみます。

$ java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)

$ java SmallPT
Samples:40 Type:master Time:PT5.8484946S

5.8秒。お、ちょっと速くなってる!

まとめ

ということで、まとめるとこう。
ついでにMacでも計測しています。Windowsが8コアでMacが4コアなので、ちょうど倍くらいの時間がかかってますね。CEのnative-imageがそれほど遅くないのだけど、コア数によるものかWindowsMacの違いか気になるところ。

GraalVM CE HotSpot GraalVM CE Native GraalVM EE HotSpot GraalVM EE Native OpenJDK 8 OpenJDK 11
Windows(i7-8Cores) 9.713 45.234 10.281 13.039 6.795 5.848
Mac(i7-4Cores) 20.966 47.069 18.797 19.135 16.783 12.594

グラフにするとこう。
f:id:nowokay:20190627045257p:plain

ということで、GraalVMのネイティブイメージは起動以外は速くない むしろ遅い、GraalVM EEのネイティブイメージはCEに比べるとかなり速いけどHotSpotモードほどではない、JDKは8から11でも速くなってる、という結果になりました。
LinuxでやればGraalは速いという噂もあります。

7/4追記 19.1.0が出てたので試してみたけど、CEのnative-imageがちょっと速くなった?

CE HotSpot CE Native EE HotSpot EE Native
19.1.0 Win 10.676 43.419 10.787 12.972