ランタイムへの多相特殊化のレポート

このガイドでは、単相化(分割)戦略を活用するために言語インプリメンタに必要なものの概要を示します。この仕組みの詳細は、分割ガイドを参照してください。

簡単に言えば、単相化ヒューリスティックは、分割によって単相状態に戻される可能性がある各ノードの多相特殊化をレポートする言語に依存します。このコンテキストでの多相特殊化とは、ノードの多相度を変更するノードのリライトです。これには、別の特殊化のアクティブ化、アクティブな特殊化のインスタンス数の増加、特殊化の除外などが含まれますが、これらに限定されません。

多相特殊化の手動レポート

多相特殊化のレポートを容易にするために、Node#reportPolymorphicSpecializeという新しいAPIがNodeクラスに導入されました。このメソッドを使用して多相特殊化を手動でレポートできますが、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ベンチマークのいずれかを実行した場合の出力のごく一部を次に示します。

...
[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ベンチマークのいずれかを実行した場合の少し簡略化された出力を次に示します。

[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.addToWritePropertyNodeです。このコール・ターゲットの既知のコール元は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.addTaskJSObjectWriteElementTypeCacheNodeです。それを行うためのすべての基準が満たされているため、このコール・ターゲットはただちに「分割が必要」とマークされます。

例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.checkPriorityAddWritePropertyNodeです。コール元が1つのみであるため、そのコール元(Scheduler.queue)を確認し、必要な基準がすべて満たされていると思われるため、「分割が必要」とマークします。

IGVへの多相特殊化のダンプ

ダンプされたデータは分割の仕組みに直接関連するため、この項の前に分割ガイドをご覧ください。

ゲスト言語コードの実行時に--engine.SplittingDumpDecisions引数をコマンドラインに追加すると、コール・ターゲットが「分割が必要」とマークされるたびに、Node#reportPolymorphicSpecializeというノードで終了するノードのチェーン(子接続および直接コール・ノードからコール先ルート・ノードへのリンクによってリンクされる)を示すグラフがダンプされます。