原文はこちら。
The original entry was written by Oleg Šelajev (Developer advocate for GraalVM at Oracle Labs).
https://medium.com/graalvm/announcing-graalvm-20-2-0-674e7f6dae27
Javaや他のJVM言語で絶対的に優れた結果を示し、JavaScript、Ruby、R、Pythonなどの一般的な言語で書かれたプログラムを実行することができる、最も先進的で最速のランタイムは、さらに優れたものになりました。
本日、GraalVM 20.2.0がリリースされました。これはfeature releaseであり、パフォーマンスやネイティブイメージテクノロジー、サポートされる言語との互換性に関する改善を数多く含んでいます。
いつものように、フィードバックやIssueを上げてくださったり、プルリクエストを送ってくださったり、プロジェクトでのGraalVMの利用方法についてお話くださったりしていただいている、すばらしいGraalVMコミュニティのみなさまに本当に感謝しています。
このエントリでは、GraalVM 20.2.0での最も注目すべき変更点を取り上げたいと思います。詳細のリストはリリースノートをご覧ください。
Release Notes
https://www.graalvm.org/release-notes/20_2/
Platform updates
全てのリリースで全てのGraalVMコンポーネントに対する数多くの修正や改善があります。コンパイラも例外ではありません。GraalVM 20.2.0でお試しいただける、2個の新機能をご紹介いたします。
GraalVM Enterpriseでは、アプリケーションがstableフェーズに入ってコンパイルがアイドルになると、libgraalが利用するメモリをOSにリリースするようになりました。
libgraal: GraalVM compiler as a precompiled GraalVM native image
https://medium.com/graalvm/libgraal-graalvm-compiler-as-a-precompiled-graalvm-native-image-26e354bee5c
https://logico-jp.io/2019/07/29/libgraal-graalvm-compiler-as-a-precompiled-graalvm-native-image/
これは各コンパイラスレッドをlibgraalのisolateにアタッチすることで実現しています。isolateにアタッチされているコンパイラのスレッド数はjvmci.ThreadsPerNativeLibraryRuntimeシステムプロパティで管理されています。デフォルトは全てのスレッドが1個のisolateにアタッチされます。isolateは追加のコンパイラスレッドを取り扱うためにオンデマンドで初期化されます。コンパイラスレッドがアイドルになると(これはjvmci.CompilerIdleDelayシステムプロパティで構成可能です)、isolateからデタッチされます。最後のスレッドがisolateからデタッチされると、isolateはシャットダウンされ、メモリ(つまりlibgraalのヒープ)をOSに返します。この機能は数多くの利点があります。
- GraalVMプロセスのRSSメモリフットプリントを削減できる。コンパイラが使われていない場合にはリソースを使わない。
- 複数のisolateを使っている場合、コンパイラスレッド間での干渉を緩和する。例えば、共有オブジェクトでの競合が減り、あるisolateでのGCが別のisolateのスレッドを停止することはない。
もうひとつのGraalVM Enterpriseに追加された注目すべき機能は、loop unrolling optimization(ループアンローリング最適化)です。これは新規の実験的機能で、デフォルトでOffになっています。loop unrollingとは伝統的なコンパイラ最適化です計算量が多く、ループが多用されているワークロードのパフォーマンスを向上させることができます。
partial loop unrollingはこの最適化を拡張したもので、ループの反復回数iの上限が不明なループに対して機能します。この変換により、2 つのバージョンのループが作成されます。メインバージョンはn回展開し、floor(i/n) 回反復を行う最適化されたループ本体に至るもの、そしてもう一つは残りの i % n(iをnで割った余り) 回の反復を行うループです。
次のJavaの例を考えてみましょう。
for(int i = 0; i < upp; i++){
// body
}
unrollFactorを2とした場合のpartial unrollingでは2個のループが作成されます。
int i1=0;
int unrollFactor=2;
for(;i1<upp-unrollFactor;i1=i1+unrollFactor){
// body i1
// body i1 +1
}
int i2=i1;
for(;i2<upp;i2++){
// body
}
partial unrollingの主要な便益はループ制御値へのジャンプや更新の回数を削減できる点です。つまり、より少ないカウンタのインクリメントとループヘッダーのジャンプで賄える、ということです。
-Dgraal.EnterprisePartialUnroll=trueを付けることで、この新たな最適化を試すことができますが、まだ実験的であり、未知のエラーが発生する可能性があることに注意してください。フィードバックを絶賛承っております。実験中に何か問題を発見した場合は、遠慮なくGraalVMチームに報告してください。
Native image
GraalVM Native Imageは引き続き非常にエキサイティングな機能であり、20.2.0ではいくつかの重要なアップデートが行われました。ここで強調したい機能のうちの2つは、デプロイの改善です。
libcのようなシステムライブラリが静的にリンクされている場合、ネイティブ実行ファイルの生成が可能になっています。この機能を使えば、例えば他に何も存在しない最小限のスクラッチイメージのDockerコンテナ内で利用可能な、完全にスタンドアロンの実行ファイルを作成できます。
20.2.0では、Muslcを使った静的コンパイルを見直し、3rdパーティーのMuslcバンドルに依存する必要がなくなりました。しかし、ネイティブの実行ファイルを生成するシステムにて動作するmusl-gccが必要です。
まだインストールされていない場合には、入手方法の手順はこちらからどうぞ。
Static native-images
https://github.com/oracle/graal/blob/master/substratevm/STATIC-IMAGES.md
インストールが正しくできたかを確認するには、以下のコマンドを実行します。
musl-gcc -v
続いて、muslcに対して静的リンクされたネイティブ実行ファイルを生成するには、以下の引数をNative Imageユーティリティに対して渡す必要があります。
--static --libc=musl
アプリケーション展開用のDockerイメージは以下のように作成できます。ここでmy-native-appが生成された実行ファイルとします。
FROM scratch
COPY my-native-app /my-native-app
ENTRYPOINT [“/my-native-app”]
空のDockerイメージが生成され、アプリケーションだけがその中に入っています。
もう一つの興味深いネイティブ実行ファイルのコンテナへのデプロイ方法は、最小限の依存関係を持つ小さなDockerイメージ、なるべくならOSを持たないDockerイメージを使うものです。例えばdistrolessイメージのようなものです。
Documentation for gcr.io/distroless/base and gcr.io/distroless/static
https://github.com/GoogleContainerTools/distroless/tree/master/base
distroless/base イメージには以下のものしか入っていません。
- ca-certificates
- root ユーザーのための /etc/passwd エントリ
- /tmp ディレクトリ
- tzdata
- glibc
- libssl
- openssl
ネイティブ実行ファイルをビルドし、コンテナが提供するlibc以外全てを静的にリンクするには、以下のフラグを付けてNative Imageユーティリティを実行します。
-H:+StaticAxeecutableWithDynamicLibC
GraalVM Enterpriseで利用可能なG1ベースのGCのNative Imageサポートも改善しました。停止時間を短縮するGC実装を試したい場合には、ビルド時に以下のオプションを付けてGCプリファレンスを指定してください。
-H:+UseLowLatencyGC
ネイティブ実行ファイルは正常にビルドされ、実行時にメモリ利用量を通常の-Xmxフラグを使って指定できます。さらに、GCアクティビティは-XX:+PrintGCフラグもしくは-XX:+VerboseGCフラグを付けて監視できます。
大事なことを言い忘れていましたが、潜在的に破壊的な変更が20.2.0に入っています。クラスの初期化戦略が変更され、ビルド時に初期化される JDK ライブラリのクラスの数が少なくなりました。
ビルド時にアプリケーションのクラスを初期化している場合、ビルド時に JDK ライブラリのクラスの初期化を引き起こす可能性がありますが、これは新しい戦略と衝突してしまいますし、そのビルドは失敗することでしょう。この問題は、必要なクラスのビルド時クラス初期化を自分で導入することで解決できます。
例えば、Micronautのサンプルアプリケーションでは、コマンドラインに
--initialize-at-build-time=com.sun.org.apache.xerces,com.sun.xml.internal,jdk.xml.internal
を追加して、20.2.0以前のリリースのようにビルド時にクラスを初期化する必要がありました。
フレームワークやライブラリに依存してネイティブイメージ生成を構成している場合は、20.2.0 で動作する最新バージョンにアップデートする必要があるかもしれません。
Truffle
Truffle 言語実装フレームワークへの重要な変更点は、言語インタープリタのデフォルトのインライン化ヒューリスティックを変更したことです。inlining budgetはTruffleノード数ではなく、Graal IR ノード数に基づいています。
これは非常に興味深い変更です。これにより、非常に重要な最適化であるインライン化が最適化コスト見積においてうまく機能します。Truffleノードは任意の複雑さを持ち、インライン化ヒューリスティックを「騙して」(tricking)たった1個のノードとして計上することにより、inlining budgetを使いすぎてしまう可能性があります。そのため、Truffleノードの個数に基づいてインライン化のコードサイズを推定すると非効率な結果になる可能性があります。
20.2.0で利用可能になった、言語実装APIに対する非常にうれしい追加が、DynamicObjectモデルとそれを利用可能にするためのライブラリです。詳細はドキュメントをご覧いただきたいのですが、簡単に言うと、動的言語でのオブジェクトレイアウトを言語実装でより良く制御できるようになり、動的にメンバーを追加したり、型を変更したりすることに対応できるようになります。これにより、シェイプによるオブジェクトの扱いが改善され、オブジェクトのプロパティへのアクセスがより効率的になります。
Dynamic Object Model
https://github.com/oracle/graal/blob/master/truffle/docs/DynamicObjectModel.md
JavaScript
GraalVM 20.2.0では、Node.jsのバージョンを12.18.0にアップデートしました。また、数多くの提案も実装しました。具体的には以下のようなものです。
- Intl.NumberFormat Unified API
https://github.com/tc39/proposal-unified-intl-numberformat - Logical Assignment Operators
https://github.com/tc39/proposal-logical-assignment - Top-lebel Await
https://github.com/tc39/proposal-top-level-await - Promise.any(ECMAScript 2021モードで利用可能)
https://github.com/tc39/proposal-promise-any
その他、asyncスタックトレースのサポートも新たに実装されましたので、お試しください。
Ruby
GraalVM 20.2.0のTruffleRubyの互換性の改善点を紹介します。リリースノートに記載されている他の多くの変更点に加えて、RipperのstdlibをC拡張を使用して実装しました。
このリリースには、互換性の改善(顕著に良くなっています)、Ruby 2.6.6.6へのアップデート、他の言語とのより良い相互運用性のサポートの追加、そして様々なパフォーマンスの改善が含まれています。
Python
GraalPythonにおける最も注目すべき変更点の中で取り上げたいのは、メモリリークを修正するためにネイティブ拡張機能の参照カウントを改善した点です。
その他にも、.pycファイル(.pycファイルは.pyファイルからコンパイルされたPythonバイトコードを永続的に保存します)にコードシリアライゼーションを導入しました。これによりPythonプログラムの起動時間が改善されるはずです。
GraalVM EnterpriseのPython実装では、_structのより高速な実装も含まれています。
Tools
ツールはGraalVMプロジェクトの非常に重要なパーツです。20.2.0ではVSCodeにおけるGraalVMのサポートで数多くの改善がなされています。
VSCode extension for GraalVM
https://www.graalvm.org/tools/vscode-extension/
Language Server Protocol (LSP) 実装では最新のプロトコルバージョンである3.15をサポートしています。GraalVMのLSPはVS Codeの通常のLSP実装に非常に興味深い機能が追加されています。これによりプログラム実行時にのみ利用可能なダイナミックな情報を通知でき、これを使ってIDEのコンテンツアシストに提案を追加できます。
全てのGraalVM VSCode拡張は1個にまとめられました。マーケットプレイスから入手できます。
Extensions for the Visual Studio family of products
https://marketplace.visualstudio.com/vscode
VSCodeではGraalVM LSPからのコードカバレッジも表示できるようになりました。
VisualVMにはGoToSourceアクションが含まれるようになりました。そのため、プロファイラからお気に入りのIDEに遷移できます。これは最も注意が必要なコードの場所に速やかに遷移するための歓迎すべき追加機能です。
ソースのルートで構成した場合、

ビューの中でクラス名やメソッド名を右クリックします。例えばCPUプロファイラで実施すると…

お気に入りのIDEでファイルが開きます。

これらはGraalVM 20.2.0機能のリリースで私たちが誇りに思っている改善点の一部に過ぎません。新機能や注目すべき機能の詳細な概要はリリースノートを一読ください。そしてGitHub上のプロジェクトコンポーネントの変更履歴をご覧ください。
GraalVM 20.2.0 Release Notes
https://www.graalvm.org/release-notes/20_2/
GraalVMリポジトリ
https://github.com/oracle/graal
GraalVM 20.2またはGraalVM Enterprise 20.2をダウンロードして、アプリケーションの実行や、新しいわくわくするプロジェクトのビルドにお使いください。ご意見・ご要望がございましたら、Twitter、Slack、GitHubでお知らせください。
GraalVM 20.2 Downloads
https://www.graalvm.org/downloads/
GraalVM 20.2 Enterprise Downloads
https://www.oracle.com/downloads/graalvm-downloads.html
Twitter
https://twitter.com/graalvm
Slack
https://www.graalvm.org/slack-invitation/
GitHub Issues
https://github.com/oracle/graal/issues