ランタイムへの多形特殊化の報告

このガイドでは、単態化(分割)戦略を活用するために言語実装者が必要なものについて概要を説明します。動作方法の詳細については、分割ガイドを参照してください。

簡単に言うと、単態化ヒューリスティックは、分割によって単態状態に戻すことができる可能性のある各ノードの多形特殊化を言語が報告することに依存しています。このコンテキストにおける多形特殊化とは、ノードの「多形性」を変えるノードの書き換えです。これには、別の特殊化の有効化、アクティブな特殊化のインスタンス数の増加、特殊化の除外などが含まれますが、これらに限定されません。

多形特殊化の手動報告 #

多形特殊化の報告を容易にするために、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.schedulecallCountは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を呼び出したノードで終わるノードのチェーン(子接続と直接呼び出しノードから呼び出し先ルートノードリンクでリンクされている)を示すグラフがダンプされます。

お問い合わせ