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
$ docker cp ~/Downloads/graalvm-cmp-gu-gvm-ins-js-njs-polynative-pro-rgx-slg-svm-tfl.tar graalvmtryout:/tmp
$docker attach graalvmtryout
[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万回作成するという重たい処理を実行するどうでもいいサンプルコード。
Sample.java
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");
}
}
$ docker cp /work/tmp/sample.jar graalvmtryout:/work/sample
[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
[root@graalvmtryout sample]# yum install glibc-devel
[root@graalvmtryout sample]# yum install zlib-devel
[root@graalvmtryout sample]# yum install gcc
[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
[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 億回作成。
Sample.java
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");
}
}
[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
先ほどの結果はあまり面白くなかったので実行回数ではなく配列のサイズを無駄に大きくしてみる。
Sample.java
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");
}
}
[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
[root@graalvmtryout sample]# ./sample
Elapsed time: 134.680 seconds
[root@graalvmtryout sample]# ./sample
Elapsed time: 134.116 seconds
追記
よくドキュメント読んだら今回の実験結果を裏付ける記述があった。
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 より若干劣るけど、いずれ改善するつもりなのでそれはあんまり言わないでおく