2022年5月
GraalVM オンハンド演習 <基礎編>
主に、下記のサイトの手順によっておりますが、初心者にも、より分かりやすい様に編集しております。
https://medium.com/graalvm/graalvm-ten-things-12d9111f307d
https://github.com/junsuzu/graalvm-jp-handson-basic/
GraalVMの主要機能下記3つを検証します。
① 最適化された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なので、こんな感じで選びます。
GraalVM Coreパッケージのインストール
上の3つをDownloadしてください。
Downloadした後に、任意のフォルダを作成して、そこに上記3つのファイルを移動させてください。(下記では、/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の生成と実行は、以下3つのライブラリーが必要です。
• 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 %
<機能1>
最適化された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)が出来たのを確認する。
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%短縮したことが分かります。
<機能2>
Native Imageの生成と実行機能
この演習の中に、GraalVMの中のAhead-of-Time(AOT)機能を利用して軽量で高速起動のNaitve Imageを作成します。
JITコンパイラはロングラン・アプリやピーク時高いスループットが要求されるアプリに強さを発揮できる一方、スタートアップ時間がかかることと、比較的に多くのメモリーを消費するデメリットがあります。
以下の例は、ファイルサイズの小さい(1KB)ファイルに対してTopTenクラスを実行した場合、起動時間と消費メモリーを測定した結果です。
graalvm-ten-thingsディレクトリーに移動します。
cd graalvm-ten-things
以下のコマンドを実行し、small.txtファイルを作成します。
make small.txt
small.txtファイルが作成されたことをlsコマンドで確認します。サイズが1KBであることを確認してください。
以下のコマンドを実行し、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
起動時間が圧倒的に減少しているのが分かると思います。
また、オプションを変えることで、メモリー消費も大幅に抑えられている事が確認できます。
<機能3>
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/ をブラウザでオープンして確認します。
====
<応用編>
一本の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!