GraalVMでRust動かしたりレイトレをネイティブコンパイルしたり

GraalVMが正式にリリースされました。結構話題になってますね。
GraalVMは、JavaベースJITとAoT、そしてASTエンジンTruffleの複合体です。(かな?)
GraalVM


ということで、Rust動かしたりJavaで書いたレイトレコードをネイティブコンパイルしたりしてみました。

Hyper-VUbuntuを用意する

ほんとはWindows Subsystem of Linux(WSL)でやりたかったのだけど、WSL上でJavaがちゃんと動いてくれなかったのであきらめました。
で、VirtualBox使うかなと思ったけど、Hyper-Vを無効にしないといけなくて、Hyper-Vを無効にするとDockerが動かなくなるのでやだなーと思ってたのだけど、普通にHyper-VUbuntuたちあげればいいのではーと思ってやってみました。


普通に使えますが、画面サイズ調整やフォルダ共有、クリップボード共有など、VirtualBoxのほうが使いやすいですね。
Macの場合はCE版ではなくOracle版を使うといいと思います。

RustをGraalVMで動かす

ASTエンジンTruffleというのは、プログラム言語を構文木に落として、最適化・実行する処理エンジンです。
JavaバイトコードLLVMのビットコードよりも抽象度が高く言語の構造が残っているため、最適化がやりやすいということを狙ってます。(たぶん)
で、そこでLLVMのビットコードをTruffleコードに変換するというSulongというツールもあるので、Rustも動きます。ライブラリなんかは、x86コードをLLVMビットコードに変換してTruffleコードに変換して動かすらしい。変態。


で、こんな感じでRustコードをmain.rsという名前で用意します。

fn main() {
  println!("Hello rust! fib(8) is {}", fib(8));
}

fn fib(i:i64) -> i64 {
  if i < 2 {
    i
  } else {
    fib(i - 2) + fib(i - 1)
  }
}


とりあえず普通にコンパイルして動かしてみます。

$ rustc main.rs
$ ./main
Hello rust! fib(8) is 21


では、これをLLVMのビットコードを吐きだしてGraalVMで動かしてみます。

$ rustc --emit=llvm-bc main.rs
$ ls -l main.*
-rw-rw-r-- 1 naoki naoki 6860  4月 23 01:37 main.bc
-rw-rw-r-- 1 naoki naoki  146  4月 23 01:36 main.rs
$ graalvm-1.0.0-rc1/bin/lli --lib $(rustc --print sysroot)/lib/libstd-* main.bc
Hello rust! fib(8) is 21

動きました!


Haskellでもいけるかなと思ったけど、LLVM3.5が必要っぽく、パッケージが3.7からしかないっぽく、とりあえずあきらめ。

レイトレをネイティブコンパイルしてみる

GraalVMにはAoTコンパイラもあります。AoTというのはAhead of Timeで、事前コンパイルのことです。これを使ってJavaコードをネイティブコンパイルしてみます。
それなりに処理量があるコードということで、こないだ作ったレイトレを動かしてみます。
kishida/smallpt4j: smallpt Java port


最新版はいろいろいじってて複数ソースに分かれてたりするので、初期のものを使います。
https://github.com/kishida/smallpt4j/blob/original/src/main/java/naoki/smallpt/SmallPT.java


パッケージをデフォルトパッケージに変更しておきます。また。FastMathを使っているのでjava.lang.Math.*をstatic importしてこちらを使います。
で、普通にコンパイルして実行

$ graalvm-1.0.0-rc1/bin/javac SmallPT.java
$ graalvm-1.0.0-rc1/bin/java SmallPT

画像ができました。

今回は早く終わるよう、画像を荒くしたままにしてます。


ではネイティブコンパイルを。
ネイティブコンパイルにはzlib.hが必要なので、とってきます。

$ sudo apt install zlib1g-dev


そしてnative-imageでコンパイル

$ graalvm-1.0.0-rc1/bin/javac SmallPT.java 
$ graalvm-1.0.0-rc1/bin/native-image SmallPT
Build on Server(pid: 7220, port: 26681)
   classlist:     166.86 ms
       (cap):     544.93 ms
       setup:     803.27 ms
    analysis:   3,460.75 ms
error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported method java.lang.ClassLoader.getParent() is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime. The unsupported element is then reported at run time when it is accessed the first time.

なんかエラー。
ImageIOがだめっぽい。動的にクラスロードしてるからだろうな。
※ 2018/11/14追記 GraalVM Community Edition 1.0 RC9ではImageIOに対応していてそのままnative-imageが通ります。


ということで、ImageIOをやめてオリジナルのsmallptと同様にppmを吐き出すように変更します。

        try(BufferedWriter bw = Files.newBufferedWriter(Paths.get("image.ppm"));
            PrintWriter pw = new PrintWriter(bw)) {
            pw.printf("P3\n%d %d\n%d\n", w, h, 255);
            for (Vec v : c) {
                pw.printf("%d %d %d ", toInt(v.x), toInt(v.y), toInt(v.z));
            }
        }

ということで、全体のコードはこちらに
SmallPT4j without ImageIO


改めてJavaで動かします。

$ graalvm-1.0.0-rc1/bin/javac SmallPT.java 
$ graalvm-1.0.0-rc1/bin/java SmallPT 
JVM:1.8.0_161 sample:40
Samples:40 Type:master Time:PT24.042S


これをネイティブコンパイル

$ graalvm-1.0.0-rc1/bin/native-image SmallPT
Build on Server(pid: 7220, port: 26681)
   classlist:     167.94 ms
       (cap):     592.71 ms
       setup:     808.36 ms
  (typeflow):   2,479.49 ms
   (objects):     875.25 ms
  (features):      24.48 ms
    analysis:   3,439.52 ms
    universe:     134.33 ms
     (parse):     318.66 ms
    (inline):     550.73 ms
   (compile):   2,101.23 ms
     compile:   3,249.17 ms
       image:     466.18 ms
       write:     120.34 ms
     [total]:   8,412.47 ms
$ ls -l smallpt
-rwxrwxr-x 1 naoki naoki 5696832  4月 23 02:27 smallpt

こんどは いけました。ネイティブファイルができてますね。そして5MB。案外小さい。


実行してみます。

$ rm image.ppm 
$ ./smallpt 
JVM:null sample:40
Samples:40 Type:master Time:PT37.341S
$ ls -l image.ppm
-rw-rw-r-- 1 naoki naoki 8244655  4月 23 02:28 image.ppm

というか、遅くなっている。。。24秒だったのが37秒に。


ついでに、通常のOpenJDKで動かしてみます。

$ jdk1.8.0_171/bin/java SmallPT 
JVM:1.8.0_171 sample:40
Samples:40 Type:master Time:PT18.565S
$ jdk-10.0.1/bin/java SmallPT 
JVM:10.0.1 sample:40
Samples:40 Type:master Time:PT16.400068S

GraalVMのJVMより速いし、JDK10はさらに速い!


ということでこんな感じに。

GraalVM native-image JDK8 JDK10
1.8.0_161 null 1.8.0_171 10.0.1
24.042 37.341 18.565 16.400068

というか、Durationの精度もJDK10であがってますね。


ということで、いろいろやってみました。
Swingアプリも試してみたけど、AppContextの初期化でインプットメソッドの処理をしようとしたときに動的クラスロードしてるっぽくてそこでダメですね。
まだまだ問題はありそうだけど、もっと開発が進むと面白そう。