GraalVMを使ってみる

## 前書き
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公式ドキュメント](https://www.graalvm.org/)によると、Graalには以下の特徴がある。


> Graal自体がjavaで実装されているので、標準のJITコンパイラではできなかった部分的なエスケープ分析などの強力な最適化により、Javaプログラムを大幅に高速化できる


実際にどのくらい速くなるのかは後述している。

ちなみに、VM変更するのは面倒であれば、以下のオプションをつけることで、JITコンパイラだけGraalに変えることもできるらしい。
しかし、[参考](https://xuwei-k.hatenablog.com/entry/2020/05/16/175252)によるとJDK10以降でしか使えないので注意。

```shell
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
```

特徴は以下の資料にまとめている
https://drive.google.com/file/d/1UKw_7dM-2SLQhnrM5cq4hemL9yqDxhPI/view?usp=sharing

## GraalVMのアーキテクチャ
以下のようなアーキテクチャになっている。
標準JVMと比べて、C2部分がGraalコンパイラに置き換わっている。
GraalとJVMCIの部分が、Javaで実装されている。

![Graal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150144/1d378bef-4855-ddf8-7945-b37a5796ba4c.png)


### JVMCI
Javaで実装されたコンパイラを動的コンパイラとしてJVMで利用できるようにしたもの。

https://openjdk.java.net/jeps/243

## 実際に使ってみる

### インストール

[公式ドキュメント](https://www.graalvm.org/getting-started/)に書いている通りにする。
macOSの場合は、[ここ](https://www.graalvm.org/docs/getting-started/macos)

### OpenJDKの標準VMとGraalVMで速度比較してみる

より詳細な速度比較は、[こちらの記事](https://qiita.com/kinshotomoya/items/f056ed2222d08aa065c8)に書いている。

275Mbの`GraalTest.txt` に含まれている単語の種類からトップ10を吐き出すプログラムを実行する。

```shell

$ 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として利用。

```shell
$ 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)
```

以下、実行プログラム

```scala:GraalTest.scala
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コンパイラを利用できる。

```shell:.jvmopts
-XX:-UseJVMCICompiler
-Xmx8G
```

```shell:実行
$ /usr/bin/time sbt run GraalTest.scala
```

すると、以下のような結果が得られる。

```shell:JVM結果
[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上で実行してみる。

```shell:.jvmopts
-Xmx8G
```

```shell:実行
$ /usr/bin/time sbt run GraalTest.scala
```

すると、以下のような結果が得られる。

```shell:GraalVM結果
[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に関してまとめる。

## 参考

- https://medium.com/graalvm/graalvm-ten-things-12d9111f307d
- https://www.graalvm.org/docs/examples/java-performance-examples/