- GraalVM for JDK 23 (最新)
- GraalVM for JDK 24 (アーリーアクセス)
- GraalVM for JDK 21
- GraalVM for JDK 17
- アーカイブ
- 開発ビルド
- Truffle 言語実装フレームワーク
- Truffle ブランチのインストルメンテーション
- 動的オブジェクトモデル
- 静的オブジェクトモデル
- インタプリタコードのホスト最適化
- Truffleによる関数インライン化のアプローチ
- Truffle インタプリタのプロファイリング
- Truffle Interop 2.0
- 言語実装
- Truffle を使用した新しい言語の実装
- Truffle 言語とインストルメントの Java モジュールへの移行
- Truffle ネイティブ関数インターフェース
- Truffle インタプリタの最適化
- オプション
- オンスタック置換
- Truffle 文字列ガイド
- 特殊化ヒストグラム
- テスト DSL 特殊化
- Polyglot API ベースの TCK
- Truffle のコンパイルキューへのアプローチ
- Truffle ライブラリガイド
- Truffle AOTの概要
- Truffle AOTコンパイル
- 補助エンジンキャッシング
- Truffle 言語セーフポイント チュートリアル
- 単態化
- 分割アルゴリズム
- 単態化のユースケース
- ランタイムへの多形特殊化の報告
ランタイムへの多形特殊化の報告
このガイドでは、単態化(分割)戦略を活用するために言語実装者が必要なものについて概要を説明します。動作方法の詳細については、分割ガイドを参照してください。
簡単に言うと、単態化ヒューリスティックは、分割によって単態状態に戻すことができる可能性のある各ノードの多形特殊化を言語が報告することに依存しています。このコンテキストにおける多形特殊化とは、ノードの「多形性」を変えるノードの書き換えです。これには、別の特殊化の有効化、アクティブな特殊化のインスタンス数の増加、特殊化の除外などが含まれますが、これらに限定されません。
多形特殊化の手動報告 #
多形特殊化の報告を容易にするために、Node
クラスに新しいAPIが導入されました。 Node#reportPolymorphicSpecialize。このメソッドを使用して多形特殊化を手動で報告できますが、DSL を使用して自動化できない場合のみです。
多形特殊化の自動報告 #
Truffle DSL は特殊化間の多くの遷移を自動化するため、多形特殊化の自動報告のためのアノテーションである @ReportPolymorphism が追加されました。このアノテーションは、DSL に特殊化後に多形性のチェックを含め、必要に応じて Node#reportPolymorphicSpecialize
を呼び出すように指示します。
このアノテーションの使用方法の例として、com.oracle.truffle.sl.nodes.SLStatementNode
を考えてみましょう。これはすべての SimpleLanguage ノードの基本クラスであり、ReportPolymorphism
アノテーションは継承されるため、このクラスにアノテーションを付けるだけで、すべての SimpleLanguage ノードの多形特殊化の報告が可能になります。以下は、このアノテーションを SLStatementNode
に追加する変更の差分です。
diff --git
a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLStatementNode.java
b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLStatementNode.java
index 788cc20..89448b2 100644
---
a/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLStatementNode.java
+++
b/truffle/src/com.oracle.truffle.sl/src/com/oracle/truffle/sl/nodes/SLStatementNode.java
@@ -43,6 +43,7 @@ package com.oracle.truffle.sl.nodes;
import java.io.File;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.GenerateWrapper;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
@@ -62,6 +63,7 @@ import com.oracle.truffle.api.source.SourceSection;
*/
@NodeInfo(language = "SL", description = "The abstract base node for all SL
statements")
@GenerateWrapper
+@ReportPolymorphism
public abstract class SLStatementNode extends Node implements
InstrumentableNode {
private static final int NO_SOURCE = -1;
多形特殊化の自動報告の制御 #
特定のノードと特殊化を除外する
言語のすべてのノードに ReportPolymorphism
アノテーションを適用することは、単態化を容易にする最も簡単な方法ですが、必ずしも意味がない場合に多形特殊化の報告を引き起こす可能性があります。言語開発者が、どのノードとどの特殊化が多形性の報告の対象となるかをより細かく制御できるように、クラス(クラス全体の自動報告を無効にする)または個々の特殊化(多形性のチェック時の考慮からこれらの特殊化を除外する)に適用できるアノテーションである @ReportPolymorphism.Excludeが導入されました。
メガモルフィックケースのみの報告
バージョン 20.3.0 から、新しいアノテーション ReportPolymorphism.Megamorphic が追加されました。このアノテーションは特殊化にのみ適用でき、高価な「汎用」特殊化(単態化によって修正されることを意図している)としてその特殊化をマークします。このアノテーションを追加すると、アノテーションされた特殊化がアクティブになると、他の特殊化の状態に関係なく、ノードはランタイムに多形性を報告します。
このアノテーションは、@ReportPolymorphism
とは別に使用できます。つまり、メガモルフィックアノテーションが機能するためには、ノードに @ReportPolymorphism
アノテーションを付ける必要はありません。両方のアノテーションを使用する場合は、多形とメガモルフィックの両方のアクティベーションが多形として報告されます。
ツールのサポート #
どのノードが多形特殊化を報告すべきか、すべきでないかを判断するのは言語開発者です。これは、ドメイン知識(言語のどのノードが多形の場合に高価であるか)または実験(特定のノード/特殊化を含める/除外することの影響を測定する)によって行うことができます。言語開発者が多形特殊化の報告の影響をよりよく理解できるように、いくつかのツールのサポートが提供されています。
個々の分割のトレース
ゲスト言語コードを実行するときにコマンドラインに--engine.TraceSplitting
引数を追加すると、ランタイムが行う各分割に関する情報がリアルタイムで出力されます。
フラグを有効にしてJavaScriptベンチマークの1つを実行した出力の一部を以下に示します。
...
[engine] split 0-37d4349f-1 multiplyScalar |ASTSize 40/ 40 |Calls/Thres 2/ 3 |CallsAndLoop/Thres 2/ 1000 |Inval# 0 |SourceSection octane-raytrace.js~441-444:12764-12993
[engine] split 1-2ea41516-1 :anonymous |ASTSize 8/ 8 |Calls/Thres 3/ 3 |CallsAndLoop/Thres 3/ 1000 |Inval# 0 |SourceSection octane-raytrace.js~269:7395-7446
[engine] split 2-3a44431a-1 :anonymous |ASTSize 28/ 28 |Calls/Thres 4/ 5 |CallsAndLoop/Thres 4/ 1000 |Inval# 0 |SourceSection octane-raytrace.js~35-37:1163-1226
[engine] split 3-3c7f66c4-1 Function.prototype.apply |ASTSize 18/ 18 |Calls/Thres 7/ 8 |CallsAndLoop/Thres 7/ 1000 |Inval# 0 |SourceSection octane-raytrace.js~36:1182-1219
...
分割サマリーのトレース
ゲスト言語コードを実行するときにコマンドラインに--engine.TraceSplittingSummary
引数を追加すると、実行が完了した後に、分割に関する収集されたデータのサマリーが出力されます。これには、分割の数、分割予算のサイズとその使用量、強制された分割の数、分割対象名のリストとその分割回数、多形特殊化を報告したノードのリストとその数が含まれます。
フラグを有効にしてJavaScriptベンチマークの1つを実行した出力の簡略版を以下に示します。
[engine] Splitting Statistics
Split count : 9783
Split limit : 15342
Split count : 0
Split limit : 574
Splits : 591
Forced splits : 0
Nodes created through splitting : 9979
Nodes created without splitting : 10700
Increase in nodes : 93.26%
Split nodes wasted : 390
Percent of split nodes wasted : 3.91%
Targets wasted due to splitting : 27
Total nodes executed : 7399
--- SPLIT TARGETS
initialize : 60
Function.prototype.apply : 117
Array.prototype.push : 7
initialize : 2
magnitude : 17
:anonymous : 117
add : 5
...
--- NODES
class ANode : 42
class AnotherNode : 198
class YetAnotherNode : 1
...
多形特殊化のトレース
このセクションの前に分割ガイドを参照してください。ダンプされたデータは、分割の動作に直接関係しているためです。
多形性の報告がどの呼び出しターゲットの分割の対象となるかに影響するかをよりよく理解するために、--engine.SplittingTraceEvents
オプションを使用できます。このオプションは、リアルタイムで、どのノードが多形性を報告しており、それが呼び出しターゲットにどのように影響しているかを詳細に記述したログを出力します。以下の例を参照してください。
例1
[engine] [poly-event] Polymorphic event! Source: JSObjectWriteElementTypeCacheNode@e3c0e40 WorkerTask.run
[engine] [poly-event] Early return: false callCount: 1, numberOfKnownCallNodes: 1 WorkerTask.run
このログセクションは、WorkerTask.run
メソッド内のJSObjectWriteElementTypeCacheNode
が多形になり、それを報告したことを示しています。また、これはWorkerTask.run
が実行される最初であること(callCount: 1
)も示しているため、「分割が必要」とはマークされません(Early return: false
)。
例2
[engine] [poly-event] Polymorphic event! Source: WritePropertyNode@50313382 Packet.addTo
[engine] [poly-event] One caller! Analysing parent. Packet.addTo
[engine] [poly-event] One caller! Analysing parent. HandlerTask.run
[engine] [poly-event] One caller! Analysing parent. TaskControlBlock.run
[engine] [poly-event] Early return: false callCount: 1, numberOfKnownCallNodes: 1 Scheduler.schedule
[engine] [poly-event] Return: false TaskControlBlock.run
[engine] [poly-event] Return: false HandlerTask.run
[engine] [poly-event] Return: false Packet.addTo
この例では、多形特殊化のソースはPacket.addTo
内のWritePropertyNode
です。この呼び出しターゲットには既知の呼び出し元が1つしかないため、呼び出しツリー内の親(つまり、呼び出し元)を分析できます。これは、例ではHandlerTask.run
であり、TaskControlBlock.run
にも同じことが適用され、同様にScheduler.schedule
にも適用されます。Scheduler.schedule
のcallCount
は1であるため、これは最初のエグゼキューションであり、「分割が必要」とはマークされません(Early return: false
)。
例3
[engine] [poly-event] Polymorphic event! Source: JSObjectWriteElementTypeCacheNode@3e44f2a5 Scheduler.addTask
[engine] [poly-event] Set needs split to true Scheduler.addTask
[engine] [poly-event] Return: true Scheduler.addTask
この例では、多形特殊化のソースはScheduler.addTask
内のJSObjectWriteElementTypeCacheNode
です。この呼び出しターゲットは、そうするためのすべての基準を満たしているため、すぐに「分割が必要」とマークされます。
例3
[engine] [poly-event] Polymorphic event! Source: WritePropertyNode@479cbee5 TaskControlBlock.checkPriorityAdd
[engine] [poly-event] One caller! Analysing parent. TaskControlBlock.checkPriorityAdd
[engine] [poly-event] Set needs split to true Scheduler.queue
[engine] [poly-event] Return: true Scheduler.queue
[engine] [poly-event] Set needs split to true via parent TaskControlBlock.checkPriorityAdd
[engine] [poly-event] Return: true TaskControlBlock.checkPriorityAdd
この例では、多形特殊化のソースはTaskControlBlock.checkPriorityAdd
内のWritePropertyNode
です。呼び出し元が1つしかないため、その呼び出し元(Scheduler.queue
)を確認し、必要なすべての基準を満たしているように見えるため、「分割が必要」とマークします。
IGVへの多形特殊化のダンプ
このセクションの前に分割ガイドを参照してください。ダンプされたデータは、分割の動作に直接関係しているためです。
ゲスト言語コードを実行するときにコマンドラインに--engine.SplittingDumpDecisions
引数を追加すると、呼び出しターゲットが「分割が必要」とマークされるたびに、Node#reportPolymorphicSpecialize
を呼び出したノードで終わるノードのチェーン(子接続と直接呼び出しノードから呼び出し先ルートノードリンクでリンクされている)を示すグラフがダンプされます。