前書き
GraalVMのパフォーマンス、しいては、GraalをJITコンパイラとして利用した際のパフォーマンスにフォーカスした記事なので、GraalVMの特徴の一つであるnative imageや、multiple languagesなどに関しては、この記事では触れていない。
環境
- macOS Mojava 10.14.4
- Scala 2.13.1
- sbt 1.3.8
- GraalVM 20.1
GraalVMとは
Javaで書かれた最新のJITコンパイラ、Graalを搭載したVMである。
GraalVM公式ドキュメントによると、Graalには以下の特徴がある。
Graal自体がjavaで実装されているので、標準のJITコンパイラではできなかった部分的なエスケープ分析などの強力な最適化により、Javaプログラムを大幅に高速化できる
実際にどのくらい速くなるのかは後述している。
ちなみに、VM変更するのは面倒であれば、以下のオプションをつけることで、JITコンパイラだけGraalに変えることもできるらしい。
しかし、参考によるとJDK10以降でしか使えないので注意。
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
特徴は以下の資料にまとめている
https://drive.google.com/file/d/1UKw_7dM-2SLQhnrM5cq4hemL9yqDxhPI/view?usp=sharing
GraalVMのアーキテクチャ
以下のようなアーキテクチャになっている。
標準JVMと比べて、C2部分がGraalコンパイラに置き換わっている。
GraalとJVMCIの部分が、Javaで実装されている。
JVMCI
Javaで実装されたコンパイラを動的コンパイラとしてJVMで利用できるようにしたもの。
実際に使ってみる
インストール
公式ドキュメントに書いている通りにする。
macOSの場合は、ここ
OpenJDKの標準VMとGraalVMで速度比較してみる
より詳細な速度比較は、こちらの記事に書いている。
275MbのGraalTest.txt
に含まれている単語の種類からトップ10を吐き出すプログラムを実行する。
$ ll
total 65368
-rw-r--r-- 1 kinsho staff 202B 5 31 14:04 FibTest.scala
-rw-r--r-- 1 kinsho staff 1.9K 5 31 17:13 GraalTest.scala
-rw-r--r-- 1 kinsho staff 275M 5 31 16:54 GraalTest.txt
drwxr-xr-x 4 kinsho staff 128B 5 31 14:28 project
drwxr-xr-x 5 kinsho staff 160B 5 31 14:35 target
上述でインストールした、GraalVMをVMとして利用。
$ java -version
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-b09)
OpenJDK 64-Bit Server VM GraalVM CE 20.1.0 (build 25.252-b09-jvmci-20.1-b02, mixed mode)
以下、実行プログラム
import java.io.File
import scala.annotation.tailrec
import scala.io.BufferedSource
object GraalTest extends App {
val fileName: String = "GraalTest.txt"
val encode: String = "UTF-8"
val source: BufferedSource = scala.io.Source.fromFile(fileName, encode)
val lines: Iterator[String] = source.getLines()
val sortedTextList = lines.toList.mkString(" ").split(" ").sorted.toList
val value = createMap(sortedTextList)
val top10Words = value.toList.sortBy(_._2).reverse.take(10)
def createMap(wordList: List[String]): Map[String, Long] = {
@tailrec
def loop(list: List[String], acc: Map[String, Long]): Map[String, Long] = {
wordList match {
case head :: tail if acc.isEmpty => {
loop(tail, acc + (head -> 1L))
}
case head :: tail => {
acc.get(head) match {
case Some(value) => {
loop(tail, acc.updated(head, value + 1L))
}
case None => {
loop(tail, acc + (head -> 1L))
}
}
}
case head :: Nil => {
acc.get(head) match {
case Some(value) => {
acc.updated(head, value + 1L)
}
case None => {
acc + (head -> 1L)
}
}
}
}
}
loop(wordList, Map.empty[String, Long])
}
}
結果
OpenJDKの標準VMでは!?
比較のために、OpenJDKの標準JITコンパイラを使ってプログラムを実行してみる。
-XX:-UseJVMCICompiler
を指定すると、OpenJDKの標準JITコンパイラを利用できる。
-XX:-UseJVMCICompiler
-Xmx8G
$ /usr/bin/time sbt run GraalTest.scala
すると、以下のような結果が得られる。
[info] running GraalTest
List((the,3297996), (and,2198664), (of,2198664), (you,1648998), (a,1648998), (in,1648998), (about,1099332), (always,1099332), (with,1099332), (how,1099332))
[success] Total time: 64 s (01:04), completed 2020/06/04 0:38:37
85.31 real 479.11 user 8.89 sys
GraalVMでは!?
上記のプログラムをGraalVM上で実行してみる。
-Xmx8G
$ /usr/bin/time sbt run GraalTest.scala
すると、以下のような結果が得られる。
[info] running GraalTest
List((the,3297996), (and,2198664), (of,2198664), (you,1648998), (a,1648998), (in,1648998), (about,1099332), (always,1099332), (with,1099332), (how,1099332))
[success] Total time: 54 s, completed 2020/06/04 0:40:02
75.29 real 333.95 user 9.23 sys
GraalVMを使うことで、10sも早くなっていることがわかる。
これは、だいぶ早くなっている!!!!!
↓パフォーマンス測定で使ったコード達
https://github.com/kinshotomoya/loginfrastructure/tree/master/jitCompiler
まとめ
現状のJITコンパイラの代わりにGraalを利用するのは、使えるなら使うべき。
次回は、GraalVMのもう一つの特徴である、native-imageに関してまとめる。