Truffle AOTの概要
Truffleでは、AOT事前初期化、コンパイルおよびキャッシングの様々な機能がサポートされています。このドキュメントでは、これらの機能の概要を示します。
ここで説明する機能には、Oracle GraalVMでのみサポートされているものもあります。
最初のコンテキストの事前初期化
ネイティブ・イメージを使用すると、イメージ・ビルド時に静的イニシャライザでJavaコードを実行できます。静的初期化が実行された後、静的フィールドから参照される値のスナップショットが取得されてイメージに保持されます。コンテキストの事前初期化では、この機能を利用し、実行時に分離またはプロセスで作成される最初のコンテキストで使用する言語コンテキストをイメージ・ビルド時に作成して初期化します。通常は、これによって最初のコンテキストの初期化時間が大幅に短縮されます。
コンテキストの事前初期化を有効にするには、イメージ・ビルド時にシステム・プロパティ-Dpolyglot.image-build-time.PreinitializeContexts=ruby,llvm
を設定します。言語がTruffleLanguage.patchContextを実装し、コンテキストの事前初期化をサポートするためにtrueを返す必要があります。また、言語がホスト固有のデータをバインドしたり、java.lang.Threadインスタンスのようなネイティブ・イメージに格納できないオブジェクトを作成したりしないように注意する必要があります。
詳細は、TruffleLanguage.patchContext javadocを参照してください。
同じ分離/プロセス内でのコード共有
コンテキスト間でのコード共有の範囲を決定するために、ポリグロット・エンジンを使用できます。その方法の例は、リファレンス・マニュアルを参照してください。ポリグロット・コンテキストで言語が初期化されると、エンジンから新しい言語インスタンスが要求されます。その言語がContextPolicy.SHAREDをサポートしていれば、言語インスタンスはエンジン・インスタンスに再利用されます。ソース解析キャッシュは言語インスタンスに関連付けられているため、解析は言語インスタンスごとに1回行われます。言語にTruffleLanguage.areOptionsCompatibleを実装することで、新しい追加コンテキストでの言語インスタンスの再利用を禁止するよう選択することもできます。これにより、言語は、その言語で作成されたすべてのルート・ノードについて、特定のコンテキスト・オプションをこれ以上編集できないとみなすことができます。ただし、InteropLibrary
はこのルールの例外で、言語インスタンス間で無条件にノードを共有できます。
コンテキストに依存しないコードのサポート
コード共有では、すべてのコード・データ構造がコンテキストに依存していないことが必要です。たとえば、あるコンテキストでコードを実行した後、コードを脱最適化せずに新しいコンテキストで再度実行できる場合、そのコードはコンテキストに依存していません。言語実装がコンテキストに依存していないことを確認するテストとして、明示的なエンジンを使用してコンテキストを作成し、テスト・アプリケーションを実行してから、同じ決定論的アプリケーションの実行時に2番目のコンテキストが脱最適化を引き起こさないことを確認することをお薦めします。
Truffleフレームワークは、通常は最初のコンテキストの作成前でも、TruffleLanguage.initializeMultipleContextsをコールして、言語インスタンスを複数のコンテキストで使用する可能性を通知します。明示的なエンジンが使用されている場合、または--engine.CacheStore
がtrue
に設定されている場合、フレームワークは最初のコンテキストの作成前に複数のコンテキストを初期化できます。
コンテキストに依存しないコードをサポートする場合は、次の基準を満たす必要があります:
- 複数のコンテキストが初期化されている場合は、ランタイム値アイデンティティの投機をすべて無効にする必要があります。これを2番目のコンテキストで使用すると、必ず脱最適化を引き起こすためです。
- 関数インライン・キャッシュは、2レベルのインライン・キャッシュとして変更および実装する必要があります。最初のレベルは関数インスタンスのアイデンティティを、2番目のレベルは基礎となるCallTargetインスタンスを投機します。複数のコンテキストが初期化されている場合は、最初のレベルのキャッシュを無効にする必要があります。これが不要な脱最適化を引き起こすためです。
- DynamicObjectルートShapeインスタンスは、言語コンテキストではなく言語インスタンスに格納する必要があります。そうしないと、シェイプ上のインライン・キャッシュが安定せず、最終的に汎用状態になりません。
- いずれのノード実装にも、コンテキストに依存するデータ構造や、コンテキストに依存するランタイム値を格納してはいけません。
- 言語内部のビルトインがあっても、TruffleLanguage.Env.parseを使用してソースのロードおよび解析を実行し、言語インスタンスごとにソース解析をキャッシュする必要があります。
- すべての仮定インスタンスは、コンテキストではなく言語インスタンスに格納する必要があります。複数のコンテキストが初期化されている場合、コンテキスト参照を使用して読み取られるコンテキスト・インスタンスが定数でなくなる可能性があります。この場合、コンテキストから読み取られた仮定は畳み込まれず、実行時のパフォーマンス・オーバーヘッドが大きくなります。シングル・コンテキスト・モードとマルチ・コンテキスト・モードのどちらでも、言語からの仮定はコンパイラによって畳み込むことができます。
複数のコンテキストに対して作成されたASTは、ランタイム値のアイデンティティの投機を許可しないため、より効率の悪いマシン・コードにコンパイルされることが予測されます。たとえば、インライン・キャッシュ内の関数インスタンスを投機するのではなく、含まれているCallTargetを投機することが必要です。この方が、関数に格納されたCallTargetにアクセスするために追加の読取りが必要になるために時間がかかります。コンテキストに依存しないコードを作成するとコストがかかる可能性があるため、複数のコンテキストが初期化されていない場合でも、ランタイム値の投機を実行する必要があります。
SimpleLanguageとJavaScriptは、コンテキストに依存しないコードをすでにサポートしている2つの言語であり、具体的な問題のガイダンスとして役立ちます。
補助エンジン・キャッシュを使用する、コンテキストに依存しない永続的なコード(Oracle GraalVM)
Oracle GraalVMでは、コード・データ構造をディスクに保持することがサポートされています。これにより、分離/プロセスでのアプリケーションの初回実行のウォームアップ時間をほぼ排除できます。SVM補助イメージ機能を使用して、必要なデータ構造をディスクに保持し、ロードします。イメージの保持には、コンパイルを実行する必要があるため、長い時間がかかる場合があります。ただし、ロードはできるだけ速く、通常はほぼ瞬時に行うように設計されています。
明示的なエンジンを使用せずにコンテキストが作成された場合でも、エンジン・キャッシングはオプションを使用して有効になり、機能しています。
エンジン・キャッシングの詳細は、エンジン・キャッシングのチュートリアルを参照してください。
プロファイリングなしのコンパイル
デフォルトでは、作成されたが実行されていない言語関数は、補助エンジン・キャッシュ・イメージに格納されるときにコンパイルされません。補助エンジン・キャッシングでは、ロードされたが実行はされていないルート・ノードのコンパイルのトリガーがサポートされています。このような場合、フレームワークはRootNode.prepareForAOTメソッドをコールします。
事前の実行なしで言語実装をコンパイルできるようにする方法の詳細は、AOTのチュートリアルを参照してください。すべての言語を事前の実行なしでコンパイルし、効率的なマシン・コードを生成できるわけではありません。通常、これには静的型付け言語の方が適しています。
アプリケーションのスナップショット
また、ポリグロット・コンテキスト・インスタンスのランタイム値をディスクに保持するためのサポートも計画されています。この機能が実装された時点で、詳細情報がここに記載されます。