GraalVM: 多言語対応のVMとJVM - Oracle€¦ ·...
Transcript of GraalVM: 多言語対応のVMとJVM - Oracle€¦ ·...
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
06
//the leading edge/
GraalVMは、さまざまなプログラミング言語を実行でき、高パフォーマンスで埋込み可能な多言語仮想マシン
です。たとえば、以下のような言語を実行できます。
■ JVMベースの言語:Java、Scala、Kotlin、Groovyなど
■ インタプリタ言語:JavaScript、Ruby、R、Pythonなど
■ LLVMに対応するネイティブ言語:C、C++、Rust、Swiftなど
多言語アプリケーションを効率よくサポートするGraalVMを使うと、パフォーマンスに重大なオーバーヘッドをかけ
ることなく、1つのプロセス内で言語を混在させることができます。そのため、目下の問題にもっとも適したソリュー
ションを組み込めるようになります。
GraalVMは、さまざまな環境でプログラムを実行できるように設計されており、JVM内で実行すること、スタ
ンドアロンのネイティブ・イメージにコンパイルして実行すること、そしてJavaモジュールとネイティブ・コード・モ
ジュールの両方を含む大きなアプリケーションに埋め込んで実行することが可能です。本記事では、GraalVMの機
能、日々の作業で使ってみる方法、そしてプロジェクトの注目すべき部分について、簡単に紹介します。
GraalVMのコンポーネント大規模なプロジェクトであるGraalVMは、いくつかの柔軟な部分によって、多彩な機能を実現しています。たとえ
ば、GraalVMでは、Javaコードを非常に高速に実行し、Nashornスクリプトの代わりにNode.jsアプリケーションを実
行し、さらにRubyやPython、Rを実行することができます。また、Javaアプリケーションを実行可能なネイティブ・
イメージにコンパイルすることもできます。イメージは、Dockerコンテナでわずか数メガバイトに収まり、数ミリ秒で
起動できます。JavaScriptコードをデータベース内のストアド・プロシージャとして実行することもでき、そうしても、
データベースで想定されるリソースを大量に消費することはありません。
GraalVM: 多言語対応のVMとJVM1つのプロジェクトで複数の言語を容易に組み合わせ、事前コンパイルのメリットを活用する
OLEG ŠELAJEV
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
07
//the leading edge/
GraalVMはオープンソースであり、おもにJavaで書かれていることから、プロジェクトに精通するために、ネイ
ティブ・コードの専門家である必要はありません。つまり、日々のJava開発に使用するツールで、GraalVMを試すこと
や開発することもできます。ここからは、いくつかのGraalVMコンポーネントを簡単に紹介します。
Graal JITコンパイラ:優れたJust-In-Time(JIT)コンパイラがなければ、高パフォーマンスな仮想マシンを実現するのはほぼ非常に困難です。そのため、GraalVMの中心には、Graalというコンパイラが据えられています。
Graalは、JITコンパイラとしても、静的な事前コンパイラとしても利用できます。
GraalVMの他のコンポーネントと同じように、GraalもJavaで書かれており、共通部分式の削除、デッドコード
の削除、定数の畳み込みなど、典型的なコンパイラ最適化が実装されています。しかし、群を抜いて優れているの
は、インライン化とエスケープ分析アルゴリズムです。Graalは、積極的に最適化を行うコンパイラです。その際に、
Java HotSpot VMのC2コンパイラが使用するプログラム依存グラフに似た内部表現(IR)を使用しますが、重要な
点で異なります。IRは、コンパイル時に、高レベル操作(Javaフィールドのロードなど)を表すものから、低レベル操
作(オフセットを加えたアドレスの読取りなど)を表すものに変換されます。この低レベル表現は、最終的にマシ
ン・コードに変換されます。GraalVMディストリビューションには、JITコンパイルの解析に役立つ標準JVMオプション
(-XX:+PrintCompilation、XX:+PrintAssemblyなど)に加え、Ideal Graph Visualizerと呼ばれるユーティリティが含まれています。このユーティリティを使うと、グラフ変換をデバッグして分析することができます。
Truffle:次に紹介する、GraalVMプロジェクトの主要コンポーネントは、プログラミング言語を実装するためのフレームワークであるTruffleです。Truffleは、ソース・プログラムの抽象構文木(AST)に基づく言語インタプリタの実
装に使用できるAPIを提供しています。ASTを評価するという方式は、プログラムを実行する方法としては比較的単
純なものです。そのため、インタプリタの取り扱いは、最適化を行うコンパイラの作成に比べればはるかに簡単で
す。しかし、TruffleではGraalコンパイラの助けを借りてインタプリタを最適化できるため、ピーク時のパフォーマン
スは従来のコンパイラで作成されたコードと同等か、ときにはそれよりも優れている場合もあります。
Truffleでは、実装される言語のインタプリタをコンパイルするために、部分評価と呼ばれる手法を使います。
かみ砕いて言うなら、Truffleでは、言語インタプリタとプログラムを受け取り、与えられたプログラム向けに特化し
たインタプリタを生成します。これは、実行時に収集したプロファイリング情報および型情報をツリー・ノードに付
加することによって実現しています。そのため、Truffleでは、このプロファイルを使用してプログラムを推論的に最
適化することができます。Truffleには、部分評価に対応したコンパイラを持つランタイムが必要ですが、Graalはこ
の要件にうまく当てはまります。
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
08
//the leading edge/
参考にできるTruffleベースの言語の例は数多くあります。GraalVMチームによる公式プロジェクトのみを列
挙しても、JavaScriptエンジン、LLVMビットコード・インタプリタ、Ruby実装、Python実装、R実装が存在します。
GitHubページには、その他のプログラミング言語の実装もいくつか掲載されています。Truffleの機能を説明するた
めのデモとして作成されたデモ用プログラミング言語もあります。Truffleを試してみたいという方は、ここから始め
てみてもよいでしょう。
Truffle言語でもっとも優れている点は、実行時に、各種プログラミング言語のオブジェクトに対して、すべての
インタプリタが相互運用性のある同じプロトコルを使用していることです。つまり、ランタイムの観点から見れば、
プログラムがJavaScript、Python、Ruby、またはその他の任意の実装言語、あるいはそれらの言語の組み合わせで
書かれていても、何の違いもありません。ランタイムでは、通常のコード最適化と同じように、各種の言語で書かれ
た多言語プログラムを最適化できます。言語の壁を越えるためにパフォーマンス上のオーバーヘッドがかかること
はありません。この点は、すべてのエコシステムのライブラリやモジュールを使用し、真の意味で最適なプログラミ
ング言語を選ぶことを可能にします。すなわち、これにより、欠落している機能をプロジェクトの言語で再実装する
ことではなく、解決の必要がある問題を解決することに集中できるようになるのです。
Truffleの別の利点は、言語の実装を仮想化できる
ことです。したがって、ランタイムの観点から見れば、ど
の言語も同じように見えます。これにより、各種ツール
も多言語対応になるというすばらしい可能性が生まれ
ます。たとえば、JavaScriptデバッガを使ってRubyプロ
グラムをステップ実行することや、通常はJavaプログラ
ムに対して使用するVisualVMを使ってJavaScriptプログラムのメモリ使用量を分析することができます。
ネイティブ・イメージ:GraalVMには、その他の機能もあります。その1つが、Javaで書かれた小型仮想マシンであるSubstrateVMです。SubstrateVMを使用して、Javaアプリケーションを実行可能なネイティブ・イメージにコン
パイルすることができます。GraalVMのネイティブ・イメージは、JVMがなくても実行でき、Javaクラスをロードして
初期化する必要もありません。このような特徴があるため、とても高速に起動します。Graalコンパイラでは、ネイ
ティブ・イメージを生成する際に、アプリケーションのクラスを分析して事前にマシン・コードにコンパイルします。
SubstrateVMは、ガベージ・コレクション、スレッドのスケジューリング、コードのキャッシュなど、どのような仮想マ
シンでも提供すると考えられるサービスを提供します。SubstrateVMのコードは、Graalで事前にコンパイルするこ
ともできます。そうすることにより、完全にウォームアップしたJITコンパイル済みコードほどのピーク時パフォーマ
JDKで動作するプログラムはすべて、GraalVMでサポートされます。
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
09
//the leading edge/
ンスは持たないものの、パフォーマンスが良好で実行時オーバーヘッドが少ない、ミリ秒単位で起動する実行可能
ファイルが生成されます。クラウドやサーバーレス・デプロイメントなどの一部の本番環境では、コードを長時間実
行した際のピーク時パフォーマンスよりも、起動時パフォーマンスが重要です。
また、GraalVMを他のランタイム・プラットフォームに埋め込み、多言語機能でプラットフォームを拡張すること
も可能です。現在、GraalVMを埋め込んだOracle Databaseの試験運用版ビルドがあります。このビルドを使うと、
PL/SQLではなく、JavaScriptでストアド・プロシージャを作成できます。同様の機能を利用できるMySQLプラグイン
もあるため、MySQLデータベースでもGraalVMを使用できます。一見すると、これはうわべだけの機能のように思
われるかもしれません。しかし、この設計によって、すでに知っているプログラミング言語や、既存のモジュールや
ライブラリのエコシステムを使用できる可能性が生まれます。
GraalVMを使ってみるGraalVMまたはその一部は、どれだけの労力を費やせるかに応じて、いくつかの方法で試すことができます。
先ほど述べたように、GraalVMはオープンソース・プロジェクトであり、クラスパス例外付きGPL2ライセンス
(OpenJDKと同じライセンス)に基づいています。そのため、当然ですが、コードベースからGraalVMをビルドする
ことができます。しかし、GraalVMを評価するもっとも簡単な方法は、ビルド済みのバイナリをダウンロードするこ
とです。
入手するディストリビューションはJDKに似ていますが、JavaScriptエンジンやNode.js実装、LLVMビットコー
ド・インタプリタ、そしてネイティブ・イメージ・ユーティリティもバンドルされています。
GraalVMディストリビューションをダウンロードし、アーカイブを$GRAALVM_HOMEディレクトリに解凍します。すると、GraalVMからjavaを実行できます。
> $GRAALVM_HOME/bin/java -versionjava version "1.8.0_172"Java(TM) SE Runtime Environment (build 1.8.0_172-b11)GraalVM 1.0.0-rc3 (build 25.71-b01-internal-jvmci-0.45, mixed mode)
JavaScriptプログラムを実行することもできます。たとえば、次のようにしてワンライナーを評価できます。
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
10
//the leading edge/
> $GRAALVM_HOME/bin/js -e 'console.log(1+2)'3
コマンドラインでguユーティリティを使い、Ruby、R、Pythonの試験運用版サポートをインストールすることができます。
$GRAALVM_HOME/bin/gu install {ruby|python|r}
すると、インストールした言語のコマンドライン・ランチャーをGraalVMディレクトリから利用できるようになりま
す。次に例を示します。
> $GRAALVM_HOME/bin/ruby -e 'puts 1 + 2'3
なお、ビルド済みのGraalVMディストリビューションは、OpenJDKをベースとしている点に注意してください。した
がって、JDKで動作するプログラムはすべて、GraalVMでサポートされます。同一構成でパフォーマンスを比較する
場合は、-XX:-UseJVMCICompilerスイッチを使用してGraalVMディストリビューションのGraalコンパイラを無効にし、OpenJDKが通常使用するJava HotSpot VMコンパイラを使います。
GraalVMでできることを学ぶためのスタート・ガイドもあります。「Top 10 Things to Do with GraalVM」
(GraalVMができることトップ10)という記事で行っている実験すべてや、GraalVMチームが収集したいくつかの例
を試してみてください。
パフォーマンス・テストの準備ができているプロジェクトと、パフォーマンスへのGraalVMの影響を測定するす
べてのインフラストラクチャがある場合、さまざまなマイクロベンチマークを試してみることから始めるのもよいで
しょう。例として、次のベンチマークについて考えてみます。このベンチマークは、Javaマイクロベンチマークの標準
ツールであるJava Microbenchmark Harnessで実装します。このベンチマーク法では、単純な一連のStream APIメ
ソッド呼出しを実行し、ストリーム内の数値を操作してから、すべての値を加算しています。
package org.graalvm.demos;
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
11
//the leading edge/
import org.openjdk.jmh.annotations.*;
import java.util.Arrays;import java.util.concurrent.TimeUnit;
@Warmup(iterations = 1)@Measurement(iterations = 3)@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.NANOSECONDS)@Fork(1)public class JavaSimpleStreamBenchmark {
static int[] values = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
@Benchmark public int testMethod() { return Arrays.stream(values) .map(x -> x + 1) .map(x -> x * 2) .map(x -> x + 2) .reduce(0, Integer::sum); }}
筆者のマシンでは、GraalVMを使った場合、このベンチマークはOpenJDK 1.8の場合よりも数倍高速に動作しま
す。実際に試してみたい方は、次のようにしてベンチマークのリポジトリをクローンしてください。
git clone https://github.com/graalvm/graalvm-demoscd graalvm-demos/java-simple-stream-benchmark
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
12
//the leading edge/
パフォーマンスの差を確認するためには、ビルドを行い、$GRAALVM_HOME/bin/javaと通常のOpenJDKのそれぞれで実行します。
mvn clean install$GRAALVM_HOME/bin/java -jar target/benchmarks.jar
当然ながら、これは実行時パフォーマンスをこの上なく厳密に評価しているというわけではありません。パフォー
マンスへの影響は、常に適切な方法を使って必ず自身で測定する必要があります。しかし、コードによっては、
GraalVMでの実行がJava HotSpot VMでの実行よりも大幅に高速になることはおわかりいただけたと思います。
GraalVMを使ってJavaと他の言語を統合するGraalVMの機能で特に興味深いのは、多言語機能です。そこで、GraalVMがどのように多言語アプリケーションを実
現しているのかについて注目してみます。
GraalVMの多言語APIの中心に存在するのは、Contextクラスです。Contextは、Java以外の言語(JVMバイトコードにコンパイルされるもの)すべてのグローバル・ランタイム状態を表現したものです。利用可能なすべての言
語をオンデマンドで初期化でき、目的の言語のコードを評価できます。次のスニペットは、多言語GraalVMアプリ
ケーションのもっとも簡単な例です。これは、JavaScriptの文字列を評価する通常のJavaコードです。値42を宣言
し、JavaにValueオブジェクトを返しています。
Context context = Context.create();Value result = context.eval("js", "42");assert result.asInt() == 42;
Valueは、言語間で相互に情報を伝達する仕組みです。すべてのJavaオブジェクトは、Value.asValue(Object value)メソッドを呼び出すことでValueに変換することができます。また、Valueは、Value.as(Class<T> targetType)を使用して対応するJavaオブジェクトに変換することができます。本記事では、この変換プロセスについて細かく説明す
ることはしませんが、このAPIが常に行おうとしているのは常識的な処理にすぎません。すなわち、数値は数値に、文
字列はStringに、実行可能な値は関数型インタフェースに、コレクションはコレクションに、といった変換を行ってい
ます。たとえば、以下の式はすべてtrueとなります。
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
13
//the leading edge/
context.eval("js", "'foobar'").as(String.class).equals("foobar");context.eval("js", "{foo:'bar'}").as(Map.class).get("foo").equals("bar");@FunctionalInterface interface IntFunction { int f(int value); }context.eval("js", "(function(a){a})").as(IntFunction.class).f(42) == 42;
このContextとValueを利用すれば、異なる言語で書かれたコンポーネント間でデータを渡すことができます。ただし、最新のアプリケーションでは、コンポーネントの多言語実装の詳細は、抽象化によって隠蔽されるこ
とが多いでしょう。たとえば、このデモ・アプリケーションは、CPU使用率のデータをRでSVGイメージとしてプロット
するSpring Boot Webアプリケーションです。
このアプリケーションでは、GraalVMの多言語コンテキストがSpringの@Beanとして定義されています。
@Beanpublic Context getGraalVMContext() { return Context.newBuilder().allowAllAccess(true).build();}
Rで書かれた、データを取り込んで結果をプロットする関数(ソースはリソース・ファイルに格納されています)が、
Spring Beanとして公開されています。この定義では、GraalVMコンテキストを受け取り、Rのソースを評価し、結果を
JavaのFunction<Double, String>として返しています。
@BeanFunction<Double, String> getPlotFunction(@Autowired Context ctx) { Source source = Source.newBuilder("R", rSource.getURL()).build(); return ctx.eval(source).as(Function.class);}
それ以降、このR関数は、他のJavaの関数型インタフェース実装とまったく同じように使用できます。GraalVMでサ
ポートされている他の言語も、同じようにしてJavaアプリケーションに組み込むことができます。
ORACLE.COM/JAVAMAGAZINE //////////////////// SEPTEMBER/OCTOBER 2018
14
//the leading edge/
まとめ本記事では、GraalVMプロジェクトとそのコンポーネント(Graalコンパイラ、Truffle、ネイティブ・イメージ・ユーティ
リティ)、そして多言語プログラムの最重要APIを紹介しました。さらに、GraalVMを使い始める方法についても、簡
単に触れました。
ぜひ、GraalVMを試してみてください。GraalVMによって、多くのJavaアプリケーションの動作が高速化さ
れます。高速起動というメリットを活用できるアプリケーションは多く、サポートされている他の言語(Ruby、
JavaScript、R、Pythonなど)で書かれたモジュールによる拡張が可能なアプリケーションも中にはあるかもしれま
せん。問題を見つけた方や、このすばらしいプロジェクトに参加したいという方は、GraalVMのGithubリポジトリに
アクセスしてください。</article>
Oleg Šelajev(@shelajev):Oracle Labsのデベロッパー・アドボケート。高パフォーマンスで埋込み可能な多言語仮想マシンGraalVMに携わる。VirtualJUG(オンラインJavaユーザー・グループ)およびGDG Tartu(エストニア)の主催者。空いた時間を使い、動的なシステム・アップデートとコード進化を研究テーマとして、博士号取得を目指している。2017年にJava Championに選出。