GraalVMハンズオン演習（機能編）。主要３機能を手元マシンで触ってみる！

2022年5月

GraalVM オンハンド演習 ＜基礎編＞　

主に、下記のサイトの手順によっておりますが、初心者にも、より分かりやすい様に編集しております。
https://medium.com/graalvm/graalvm-ten-things-12d9111f307d
https://github.com/junsuzu/graalvm-jp-handson-basic/

GraalVMの主要機能下記３つを検証します。
①	最適化されたJITコンパイラ機能
②	Microserviceに最適なAOT（事前コンパイル）によるNative Image作成機能
③  多言語対応機能

尚、私の手元の環境はMacですので、それに沿って、実施します。

＜環境設定／GraalVMの設定＞
OTN - Oracle Technology Network　からGraalVM Enterprise Editionをダウンロードします。下図のように"GraalVM Enterprise Edition 22 Current Release" タブを選択し、“Release Version 22.1.0, macOS x86 Java Version 11”を選択します。
https://www.oracle.com/downloads/graalvm-downloads.html

以下のコンポーネントをダウンロードします。必須コンポーネントはこの演習に必要です。その他のオプショナルコンポーネントは演習では使いませんが、今後GraalVMの多くの機能を試したい場合はダウンロードしておいてください。
	Oracle GraalVM Enterprise Edition Core（必須）
	Oracle GraalVM Enterprise Edition Native Image（必須）
	Ideal Graph Visualizer（オプショナル）
	GraalVM LLVM Toolchain Plugin（必須）
	Oracle GraalVM Enterprise Edition Ruby Language Plugin（オプショナル）
	GraalVM Enterprise Edition Python Language Plugin（オプショナル）
	Oracle GraalVM Enterprise Edition WebAssembly（オプショナル）
	GraalVM R Language Plugin (必須）
※R Pluginは直接ダウンロードできず、GraalVMのインストール・ユーティリティー(guコマンド)を使用してインストールします。)
私の環境は、Macなので、こんな感じで選びます。
![0516a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634867/9f1ca2f7-5841-3df5-bca5-77871a70af86.png)

GraalVM Coreパッケージのインストール

上の３つをDownloadしてください。
Downloadした後に、任意のフォルダを作成して、そこに上記３つのファイルを移動させてください。（下記では、/opt/mac の直下に移動しました。）

```
userX@userX-mac /opt % cd mac
userX@userX-mac mac % ls
graalvm-ee-java11-darwin-amd64-22.1.0.tar.gz
llvm-toolchain-installable-java11-darwin-amd64-22.1.0.jar
native-image-installable-svm-svmee-java11-darwin-amd64-22.1.0.jar
```

移動先のフォルダ（/opt/mac）の中で、下記のコマンドを実行して解凍してください。
（私の環境では、解凍した後に移動すると不具合が生じました。必ず、フォルダ移動後に解凍してください。）
```
userX@userX-mac mac % tar -zxf graalvm-ee-java11-darwin-amd64-22.1.0.tar.gz
```

解凍後はこんな感じ。
```
userX@userX-mac mac % ls
graalvm-ee-java11-22.1.0
graalvm-ee-java11-darwin-amd64-22.1.0.tar.gz
llvm-toolchain-installable-java11-darwin-amd64-22.1.0.jar
native-image-installable-svm-svmee-java11-darwin-amd64-22.1.0.jar
```

GraalVMへのPathを設定します。（bashの場合）

```
vi ~/.bashrc
```
以下の行を ~/.bashrc に追加します。
```
export GRAALVM_HOME=/opt/graalvm-ee-java11-22.1.0
export PATH=$GRAALVM_HOME/bin:$PATH
export JAVA_HOME=$GRAALVM_HOME
```

ファイルを修正後、以下のコマンドで実行しして、反映させます。
```
source ~/.bashrc
```

Pathが反映されるのを確認します。

```
java –version
```

以下の出力結果を確認できれば、GraalVM 22.1.0 for Java11が正常にインストールされたことになります。
```
userX@userX-mac mac % java -version
java version "11.0.15" 2022-04-19 LTS
Java(TM) SE Runtime Environment GraalVM EE 22.1.0 (build 11.0.15+8-LTS-jvmci-22.1-b05)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 22.1.0 (build 11.0.15+8-LTS-jvmci-22.1-b05, mixed mode, sharing)
```

Native Imageのインストール
Native ImageをインストールするのにGraalVM Utility(gu)を使用します。モジュールのダウンロード先（ここでは、/opt/mac/）にて下記コマンドを実行します。
```
userX@userX-mac mac % gu install -L native-image-installable-svm-svmee-java11-darwin-amd64-22.1.0.jar 
```
Native Image依存ライブラリーのインストール
Native Imageの生成と実行は、以下３つのライブラリーが必要です。
•	glibc-devel: Cライブラリーの使用に必要なヘッダーとオブジェクトファイル
•	zlib-devel: zip や gzip ライブラリーの使用に必要なヘッダーファイル
•	gcc: C/C++など複数言語のコンパイラ集

OSによりインストール・コマンドは異なります：

Ubuntu
```
sudo apt-get install build-essential libz-dev zlib1g-dev
```
Oracle Linux
```
sudo yum install gcc glibc-devel zlib-devel
```
その他Linux
```
sudo dnf install gcc glibc-devel zlib-devel libstdc++-static
```

MacOS
```
xcode-select --install
```
LLVMとR言語、Nodejsプラグインのインストール

LLVM-toolchainプラグインのインストール
以下のコマンドを実行し、必要なモジュールを自動的にgithubよりダウンロードされます。
```
gu install -L llvm-toolchain-installable-java11-darwin-amd64-22.1.0.jar 
```
R言語プラグインのインストール
以下のコマンドを実行し、必要なモジュールを自動的にgithubよりダウンロードされます。
```
gu install R
```

R言語ソースのコンフィグ
以下のコマンドでR言語ソースのコンフィグ作業を実施します。
```
/opt/mac/graalvm-ee-java11-22.1.0/Contents/Home/languages/R/bin/configure_fastr
```
以下の出力結果を確認します。
```
The basic configuration of FastR was successfull.

Note: if you intend to install R packages you may need additional dependencies.
The following packages should cover depenedencies of the most commonly used R packages:
On Debian based systems: apt-get install build-essential gfortran libxml2 libc++-dev
On Oracle Linux: yum groupinstall 'Development Tools' && yum install gcc-gfortran

Default personal library directory (/home/mluther/R/x86_64-pc-linux-gnu-library/fastr-21.0.0-3.6) does exist. Do you wish to create it? (Yy/Nn) y
Creating personal library directory: /home/mluther/R/x86_64-pc-linux-gnu-library/fastr-21.0.0-3.6
DONE
```

また、後ほど、npmも使うので、nodejsもインストールしてください。
```
gu install nodejs
```

こうなっている筈です。
これで、環境設定はお終いです。

```
userX@userX-mac bin % gu list
ComponentId              Version             Component name                Stability                     Origin 
---------------------------------------------------------------------------------------------------------------------------------
graalvm                  22.1.0              GraalVM Core                  Supported                     
R                        22.1.0              FastR                         Experimental                  github.com
js                       22.1.0              Graal.js                      Supported                     
llvm-toolchain           22.1.0              LLVM.org toolchain            Supported                     github.com
native-image             22.1.0              Native Image                  Early adopter                 gds.oracle.com
nodejs                   22.1.0              Graal.nodejs                  Supported                     gds.oracle.com
userX@userX-mac bin % 
```

＜機能１＞
最適化されたJITコンパイラ機能を触ってみる。

以下の演習は「Top 10 Things To Do With GraalVM」 の内容を使用します。
https://medium.com/graalvm/graalvm-ten-things-12d9111f307d
(1)上記内容を使用するため、Githubよりソースをダウンロードします。任意の作業ディレクトリーで以下のコマンドを実行します。（ここでは、/opt/mac/　直下で実行します。
```
git clone https://github.com/chrisseaton/graalvm-ten-things/
```

```
userX@userX-mac mac % git clone https://github.com/chrisseaton/graalvm-ten-things/
Cloning into 'graalvm-ten-things'...
remote: Enumerating objects: 61, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 61 (delta 8), reused 7 (delta 7), pack-reused 51
Receiving objects: 100% (61/61), 4.91 MiB | 7.26 MiB/s, done.
Resolving deltas: 100% (19/19), done.
```

graalvm-ten-things　がインストールされている事を確認。
```
userX@userX-mac mac % ls 
graalvm-ee-java11-22.1.0
graalvm-ee-java11-darwin-amd64-22.1.0.tar.gz
graalvm-ten-things
llvm-toolchain-installable-java11-darwin-amd64-22.1.0.jar
native-image-installable-svm-svmee-java11-darwin-amd64-22.1.0.jar
```

graalvm-ten-things のフォルダに移動して、サイズが約150MBに及ぶテキストファイル"large.txt"を作成します。

```
userX@userX-mac mac % cd graalvm-ten-things
userX@userX-mac graalvm-ten-things % make large.txt
2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500; do cat lorem.txt >> large.txt; done
```

下記コマンドで、ファイル(large.txt)が出来たのを確認する。
![0516b.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634867/4cc40749-a1d1-fa5a-1442-f4cf8ff0b030.jpeg)


```
userX@userX-mac graalvm-ten-things % ls -l
```
 
この演習で使用するサンプルプログラムTopTen.javaはlarge.txtの中から単語を集計し、上位トップテンの単語一覧を出力するJavaプログラムです。このプログラムはStream Java APIを使用し、すべての単語をソートし、カウントします。
以下はプログラムの内容です。
```
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TopTen {

    public static void main(String[] args) {
        Arrays.stream(args)
                .flatMap(TopTen::fileLines)
                .flatMap(line -> Arrays.stream(line.split("\\b")))
                .map(word -> word.replaceAll("[^a-zA-Z]", ""))
                .filter(word -> word.length() > 0)
                .map(word -> word.toLowerCase())
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                .entrySet().stream()
                .sorted((a, b) -> -a.getValue().compareTo(b.getValue()))
                .limit(10)
                .forEach(e -> System.out.format("%s = %d%n", e.getKey(), e.getValue()));
    }

    private static Stream<String> fileLines(String path) {
        try {
            return Files.lines(Paths.get(path));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}
```

TopTen.javaをコンパイルします。デフォルトではクラスパスが通るGraalVMのJITコンパイラが有効になります。
```
javac TopTen.java
```

GraalVMのJITコンパイラはJavaで書かれています。以下の最適化によりJITコンパイラの実行速度が従来C++で書かれていたコンパイラよりも速くなります。
•	Partial Escape Analysis
•	In-lining
•	Path Duplication
以下のJavaコマンドでコンパイルされたJavaクラスを実行し、実行タイムを測ります。引数にはlarge.txtを指定します。
```
time java TopTen large.txt				
```
```
userX@userX-mac graalvm-ten-things % time java TopTen large.txt
sed = 502500
ut = 392500
in = 377500
et = 352500
id = 317500
eu = 317500
eget = 302500
vel = 300000
a = 287500
sit = 282500
java TopTen large.txt  14.62s user 0.64s system 119% cpu 12.812 total
```

従来のJITコンパイラと比較するため、以下のJavaコマンドでフラッグを立てます：-XX:-UseJVMCICompile。JVMCIはGraalVMとJVMのあいだのインタフェースです。このフラッグによりJVMCIが使用されず、従来のJITコンパイラが使用されます。
```
userX@userX-mac graalvm-ten-things % time java -XX:-UseJVMCICompiler TopTen large.txt
sed = 502500
ut = 392500
in = 377500
et = 352500
id = 317500
eu = 317500
eget = 302500
vel = 300000
a = 287500
sit = 282500
java -XX:-UseJVMCICompiler TopTen large.txt  20.10s user 0.89s system 112% cpu 13.012 total
``` 
以上の結果から、GraalVMのJITコンパイラの実行時間は従来のコンパイラに比べて約30%短縮したことが分かります。

＜機能２＞
Native Imageの生成と実行機能

この演習の中に、GraalVMの中のAhead-of-Time(AOT)機能を利用して軽量で高速起動のNaitve Imageを作成します。
JITコンパイラはロングラン・アプリやピーク時高いスループットが要求されるアプリに強さを発揮できる一方、スタートアップ時間がかかることと、比較的に多くのメモリーを消費するデメリットがあります。
以下の例は、ファイルサイズの小さい（１KB)ファイルに対してTopTenクラスを実行した場合、起動時間と消費メモリーを測定した結果です。　　
graalvm-ten-thingsディレクトリーに移動します。
```
cd graalvm-ten-things
```

以下のコマンドを実行し、small.txtファイルを作成します。
```
make small.txt
```
small.txtファイルが作成されたことをlsコマンドで確認します。サイズが1KBであることを確認してください。
![0516c.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634867/aaeca5f0-a81a-aa6a-b477-1be2c0a9d2a9.jpeg)

 
以下のコマンドを実行し、small.txtの単語を集計するプログラムTopTenを実行します。
```
userX@userX-mac graalvm-ten-things % time java TopTen small.txt 
```

出力結果を確認し、実行時間とメモリーを確認します。

```
sed = 6
sit = 6
amet = 6
mauris = 3
volutpat = 3
vitae = 3
dolor = 3
libero = 3
tempor = 2
suscipit = 2
        0.67 real         0.30 user         0.08 sys
```

GraalVMが提供しているツールを使用して実行可能なNative Imageを作成します。実行ファイルの作成に少し時間がかかります。

```
userX@userX-mac graalvm-ten-things % native-image --no-server --no-fallback TopTen
Warning: Ignoring server-mode native-image argument --no-server.
========================================================================================================================
GraalVM Native Image: Generating 'topten' (executable)...
========================================================================================================================
[1/7] Initializing...                                                                                   (12.2s @ 0.10GB)
 Version info: 'GraalVM 22.1.0 Java 11 EE'
 C compiler: cc (apple, x86_64, 13.1.6)
 Garbage collector: Serial GC
[2/7] Performing analysis...  [******]                                                                  (21.8s @ 0.90GB)
   2,995 (74.32%) of  4,030 classes reachable
   3,716 (54.47%) of  6,822 fields reachable
  14,341 (48.21%) of 29,745 methods reachable
      26 classes,     0 fields, and   304 methods registered for reflection
      57 classes,    59 fields, and    51 methods registered for JNI access
[3/7] Building universe...                                                                               (1.6s @ 0.49GB)
[4/7] Parsing methods...      [*]                                                                        (1.8s @ 0.75GB)
[5/7] Inlining methods...     [****]                                                                     (1.5s @ 1.44GB)
[6/7] Compiling methods...    [*****]                                                                   (30.9s @ 4.43GB)
[7/7] Creating image...                                                                                  (2.3s @ 0.88GB)
   5.57MB (44.46%) for code area:    7,401 compilation units
   6.07MB (48.52%) for image heap:   1,792 classes and 101,197 objects
 899.28KB ( 7.02%) for other data
  12.52MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
 754.62KB java.util                                            1.07MB byte[] for code metadata
 427.69KB java.lang                                          899.66KB byte[] for general heap data
 332.91KB com.oracle.svm.core.code                           681.68KB java.lang.String
 326.97KB java.text                                          626.15KB byte[] for java.lang.String
 261.68KB java.util.concurrent                               473.38KB java.lang.Class
 258.64KB java.util.stream                                   229.09KB java.util.HashMap$Node
 211.12KB java.util.regex                                    195.69KB java.util.concurrent.ConcurrentHashMap$Node
 204.75KB com.oracle.svm.jni                                 140.39KB com.oracle.svm.core.hub.DynamicHubCompanion
 156.24KB java.util.logging                                  137.28KB char[]
 141.03KB com.oracle.svm.core.reflect                        116.65KB byte[] for reflection metadata
      ... 128 additional packages                                 ... 809 additional object types
                                           (use GraalVM Dashboard to see all)
------------------------------------------------------------------------------------------------------------------------
                        1.3s (1.7% of total time) in 23 GCs | Peak RSS: 3.55GB | CPU load: 3.23
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /opt/mac/graalvm-ten-things/topten (executable)
 /opt/mac/graalvm-ten-things/topten.build_artifacts.txt
========================================================================================================================
Finished generating 'topten' in 1m 14s.
```

これにより、軽量な実行ファイル"topten"が作成されたことを確認します。
```
yonishik_jp@yonishik_jp-mac graalvm-ten-things % ls -l
total 332056
-rw-r--r--  1 yonishik_jp  staff        797  5 16 11:29 Distance.java
-rw-r--r--  1 yonishik_jp  staff        446  5 16 11:29 Dockerfile
<中略＞
-rwxr-xr-x  1 yonishik_jp  staff   12385376  5 16 11:42 topten
-rw-r--r--  1 yonishik_jp  staff         21  5 16 11:42 topten.build_artifacts.txt
-rw-r--r--  1 yonishik_jp  staff     416373  5 16 11:29 visualvm-java.png
-rw-r--r--  1 yonishik_jp  staff     412580  5 16 11:29 visualvm-rb.png
 ```

以下のコマンドでtoptenのサイズを確認できます。
```
userX@userX-mac graalvm-ten-things % du -h topten
 12M	topten
```

以下のコマンドで、実行ファイルtoptenを実行します。引数にsmall.txtを設定します。
```
userX@userX-mac graalvm-ten-things % time ./topten small.txt   

sed = 6
sit = 6
amet = 6
mauris = 3
volutpat = 3
vitae = 3
dolor = 3
libero = 3
tempor = 2
suscipit = 2
        0.08 real         0.06 user         0.00 sys
```
起動時間が圧倒的に減少しているのが分かると思います。
また、オプションを変えることで、メモリー消費も大幅に抑えられている事が確認できます。

＜機能３＞
Polyglotプログラミングと実行

GraalVM内部ではTruffleというフレームワークを使用してJava以外のプログラミング言語をGraalVMのJITコンパイラ上で動かすことができます。
以下の演習では、Javascriptで書いた簡単なプログラムを動かしてみます。
まずNode.jsで利用できるWebアプリケーションフレームワークExpressをインストールします。以下のコマンドを実行します。
```
$GRAALVM_HOME/bin/npm install express
```
hellonode.jsを下記の様に書いて、任意のフォルダに格納します。（ここでは、binの直下に書きます。）
このプログラムの中にjavascriptを呼び出しています。


＜hellonode.js＞
```
//HTTP モジュールを読み込む
const http = require("http");
const hostname = '127.0.0.1';
const port = 3000;

//HTTP サーバーを作成し、3000 番ポートでリクエストを待機します。
const server = http.createServer((req, res) => {

  //HTTP ステータスとコンテンツタイプを持つ応答 HTTP ヘッダーを設定します。
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
 res.end('Hi, \n This is Yojiro Nishikawa(yojiro.nishikaw@oracle.com).\n Please contact me anytime on Oracle GraalVM Solution.\n LinkedIn :https://www.linkedin.com/in/%E6%B4%8B%E6%AC%A1%E9%83%8E-%E8%A5%BF%E5%B7%9D-22a268141\n');
});

//3000 番ポートでリクエストを待機し、受信したときにログ出力するコールバック関数
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
```
実行します。

```
userX@userX-mac bin % node --jvm --polyglot hellonode.js
Example app listening on port 3000!
```
実行結果を確認するため、http://localhost:3000/ をブラウザでオープンして確認します。
![0516x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634867/9b0b12bc-5835-979b-f224-179fcdb6c326.png)

====

＜応用編＞
一本のJavaScriptプログラム（polyglot.js)の中にGraalVMのpolyglot APIを使用し、JavaとRの両方を呼び出します。大きい整数の扱いがより効率的であるJavaのBigIntegerクラスを利用しながら、描画が得意とするRで3Dグラフを作成します。

＜polyglot.js＞
```
const express = require('express')
const app = express()
const BigInteger = Java.type('java.math.BigInteger')
app.get('/', function (req, res) {
 var text = '<head><style>svg { width: 100%; height: 100% }</style></head>'
 text += 'Hello World from Graal.js!<br> '
 // Using Java standard library classes
 text += BigInteger.valueOf(10).pow(100)
     .add(BigInteger.valueOf(43)).toString() + '<br>'
 // Using R interoperability to create graphs
 text += Polyglot.eval('R',
  `svg();
   require(lattice);
   x <- 1:100
   y <- sin(x/10)
   z <- cos(x^1.3/(runif(1)*5+10))
   print(cloud(x~y*z, main="cloud plot"))
   grDevices:::svg.off()
  `);
 res.send(text)
})
app.listen(3000, function () {
 console.log('Example app listening on port 3000!')
})
```

同様に実行します。（手元の環境の制限により、polyglot.jsは、Google Cloud上にUbuntuを構築して実行しました。）
```
userX@userX-Ubuntu bin % node --jvm --polyglot polyglot.js
Example app listening on port 3000!
```
![0516y.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2634867/bee0b0ef-1f95-0401-5f17-f4762469ebdd.png)