GraalVM ではJavaのネイティブ化ができるとのことなのでどれぐらい速くなるのか実験。結論からいうと、比較的小さな処理については高速化が見込まれるが、やたらデータがでかかったりループ回数が果てしない処理だとむしろ遅くなることがわかった。
実行環境
MacにDocker入れて、CentOS7コンテナを実行、その上にGraalVMをインストール。
- MacBook Air (13-inch, Early 2015)
- macOS High Sierra バージョン 10.13.3
- プロセッサ 1.6 GHz Intel Core i5
- メモリ 8 GB 1600 MHz DDR3
- Docker Version 18.03.0-ce-mac59 (23608)
- CentOS7
- GraalVM Community Edition 1.0 RC1
CentOS7コンテナの作成とGraalVMのインストール
CentOS7のコンテナを実行
$ docker run -dti -h graalvmtryout --name graalvmtryout centos:latest /bin/bash
ダウンロードしてきたGraalVMをコンテナにコピー( Mac だとダウンロード後に .gz が勝手に解凍されるので すでに tar になっている)。
$ docker cp ~/Downloads/graalvm-cmp-gu-gvm-ins-js-njs-polynative-pro-rgx-slg-svm-tfl.tar graalvmtryout:/tmp
コンテナに attach して中に入る。
$docker attach graalvmtryout
適当なディレクトリでtarを解凍、パスを通す。
[root@graalvmtryout /]# mv /tmp/graalvm-cmp-gu-gvm-ins-js-njs-polynative-pro-rgx-slg-svm-tfl.tar /opt
[root@graalvmtryout opt]# tar -xvf graalvm-cmp-gu-gvm-ins-js-njs-polynative-pro-rgx-slg-svm-tfl.tar
[root@graalvmtryout opt]# export PATH=/opt/graalvm-1.0.0-rc1/bin:$PATH
サンプルコード用のディレクトリを作成しておく。
[root@graalvmtryout opt]# mkdir -p /work/sample
サンプルコードの作成と実行1
性能測定のために Object の2次元配列インスタンスを1000万回作成するという重たい処理を実行するどうでもいいサンプルコード。
package org.test.graalvm;
public class Sample {
public static void main(String[] args) {
double st = (double)System.currentTimeMillis();
int max = 10000000;
for (int i = 0; i < max; i++) {
Object[] obj = new Object[10][10];
}
double ed = (double)System.currentTimeMillis();
System.out.println("Elapsed time: " + String.format("%.3f", (ed -st)/1000) + " seconds");
}
}
これを sample.jar という実行可能 jar ファイルに固めてコンテナにアップロード。
$ docker cp /work/tmp/sample.jar graalvmtryout:/work/sample
コンテナで java コマンドによる実行を 3 ショット実施。大体 8 秒。
[root@graalvmtryout /]# cd /work/sample
[root@graalvmtryout sample]# java -jar sample.jar
Elapsed time: 7.975 seconds
[root@graalvmtryout sample]# java -jar sample.jar
Elapsed time: 8.157 seconds
[root@graalvmtryout sample]# java -jar sample.jar
Elapsed time: 8.113 seconds
ネイティブバイナリを作成する、その前に glibc-devel、zlib-devel、gcc が必要とのことなので入れておく。
[root@graalvmtryout sample]# yum install glibc-devel
[root@graalvmtryout sample]# yum install zlib-devel
[root@graalvmtryout sample]# yum install gcc
ネイティブバイナリ化実行。さくっと出来るのかと思ったらわりと時間かかった。あと出来上がったバイナリが元の 1.7KB から 3200 倍膨れ上がって 5MB にもなってる。
[root@graalvmtryout sample]# native-image -jar sample.jar
Build on Server(pid: 192, port: 26681)
classlist: 676.35 ms
(cap): 1,509.13 ms
setup: 3,103.09 ms
(typeflow): 9,686.07 ms
(objects): 2,531.50 ms
(features): 54.01 ms
analysis: 12,417.70 ms
universe: 472.40 ms
(parse): 3,432.62 ms
(inline): 1,880.51 ms
(compile): 12,995.05 ms
compile: 18,884.15 ms
image: 1,096.32 ms
write: 391.15 ms
[total]: 37,130.09 ms
[root@graalvmtryout sample]# ls -ltr
total 5244
-rw-r--r-- 1 501 root 1655 Apr 26 09:46 sample.jar
-rwxr-xr-x 1 root root 5364232 Apr 27 00:50 sample
ネイティブバイナリの実行。50% 以上高速化してる!
[root@graalvmtryout sample]# ./sample
Elapsed time: 3.771 seconds
[root@graalvmtryout sample]# ./sample
Elapsed time: 3.849 seconds
[root@graalvmtryout sample]# ./sample
Elapsed time: 3.759 seconds
サンプルコードの作成と実行2
今度は同じコードでもう少し負荷を増やしてインスタンスを1000万 → 1 億回作成。
package org.test.graalvm;
public class Sample {
public static void main(String[] args) {
double st = (double)System.currentTimeMillis();
int max = 100000000;
for (int i = 0; i < max; i++) {
Object[] obj = new Object[10][10];
}
double ed = (double)System.currentTimeMillis();
System.out.println("Elapsed time: " + String.format("%.3f", (ed -st)/1000) + " seconds");
}
}
Java の実行、ネイティブバイナリの作成と実行。実行回数が10倍になったので大体実行時間も10倍ぐらいという順当な結果。
[root@graalvmtryout sample]# java -jar sample.jar
Elapsed time: 72.728 seconds
[root@graalvmtryout sample]# native-image -jar sample.jar
Build on Server(pid: 192, port: 26681)
classlist: 1,457.14 ms
(cap): 838.34 ms
setup: 1,325.01 ms
(typeflow): 5,672.31 ms
(objects): 1,403.75 ms
(features): 52.52 ms
analysis: 7,371.14 ms
universe: 243.41 ms
(parse): 1,554.09 ms
(inline): 1,105.42 ms
(compile): 8,194.68 ms
compile: 11,149.07 ms
image: 696.83 ms
write: 153.56 ms
[total]: 22,446.04 ms
[root@graalvmtryout sample]# ./sample
Elapsed time: 37.175 seconds
サンプルコードの作成と実行3
先ほどの結果はあまり面白くなかったので実行回数ではなく配列のサイズを無駄に大きくしてみる。
package org.test.graalvm;
public class Sample {
public static void main(String[] args) {
double st = (double)System.currentTimeMillis();
int max = 10000000;
for (int i = 0; i < max; i++) {
Object[] obj = new Object[100][100];
}
double ed = (double)System.currentTimeMillis();
System.out.println("Elapsed time: " + String.format("%.3f", (ed -st)/1000) + " seconds");
}
}
Java の実行、ネイティブバイナリの作成と実行。あれ、ネイティブの方が遅くなってる。
[root@graalvmtryout sample]# java -jar sample.jar
Elapsed time: 110.661 seconds
[root@graalvmtryout sample]# java -jar sample.jar
Elapsed time: 112.828 seconds
[root@graalvmtryout sample]# java -jar sample.jar
Elapsed time: 112.074 seconds
[root@graalvmtryout sample]# native-image -jar sample.jar
Build on Server(pid: 192, port: 26681)
classlist: 648.54 ms
(cap): 809.75 ms
setup: 1,162.87 ms
(typeflow): 4,356.15 ms
(objects): 1,212.20 ms
(features): 45.31 ms
analysis: 5,701.89 ms
universe: 233.38 ms
(parse): 1,367.30 ms
(inline): 1,228.59 ms
(compile): 6,934.35 ms
compile: 9,796.12 ms
image: 471.23 ms
write: 159.25 ms
[total]: 18,368.38 ms
[root@graalvmtryout sample]# ./sample
Elapsed time: 134.104 seconds
もう 2 ショット実施。結果変わらず。
[root@graalvmtryout sample]# ./sample
Elapsed time: 134.680 seconds
[root@graalvmtryout sample]# ./sample
Elapsed time: 134.116 seconds
サンプルコードの内容が雑なので一概には言えないかもしれないけど、そもそもネイティブ化はでかいデータをぐるぐるループするバッチのような処理には向いてないんだな……。
ちゃんとドキュメント読んでると現時点ではJavaならなんでもかんでもネイティブ化できるわけでもなく、いろんな制約があるようだ。小さくシンプルな処理をより速くということか。
追記
よくドキュメント読んだら今回の実験結果を裏付ける記述があった。
When does it make sense to run native images instead of the JVM?
- When startup time and memory footprint is important.
- When you want to embed Java code with existing C/C++ applications
What’s the typical performance profile on the SVM?
- Right now peak performance is a bit worse than HotSpot, but we don’t want to advertise that (and we want to fix it of course).
(意訳)
VMに代わってネイティブイメージを使うのはどのような場合が最適?
- 起動時間とメモリのフットプリントが小さいことが重要な場合
- C/C++ アプリケーションに Java のコードを埋め込みたい場合
一般的にSVMの性能はどんな感じ?
- 現時点でピーク処理時の性能は HotSpot より若干劣るけど、いずれ改善するつもりなのでそれはあんまり言わないでおく