向运行时报告多态特化

本指南概述了语言实现者需要满足的要求,以利用单态化(拆分)策略。有关其工作原理的更多信息,请参阅拆分指南。

简单来说,单态化启发式算法依赖于语言报告每个节点的多元专用化,这些节点可能通过拆分返回到单态状态。在此语境下,多元专用化是任何节点重写,导致节点改变其“多态性”。这包括但不限于激活另一个专用化、增加活动专用化的实例数量、排除某个专用化等。

手动报告多元专用化 #

为了方便报告多元专用化,在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注解

仅报告巨多态(Megamorphic)情况

自 20.3.0 版本起,新增了一个注解:ReportPolymorphism.Megamorphic。此注解只能应用于专用化,它将该专用化标记为巨多态(megamorphic),因为它旨在用于通过单态化解决的昂贵的“通用”专用化。添加此注解的效果是,一旦带注解的专用化变为活动状态,节点将向运行时报告多态性,而与其它专用化状态无关。

此注解可以独立于@ReportPolymorphism使用,即,节点无需@ReportPolymorphism进行注解,巨多态(megamorphic)注解也能生效。如果同时使用这两个注解,则多态和巨多态的激活都将报告为多态性。

工具支持 #

语言开发者需要确定哪些节点应该报告,哪些不应该报告多元专用化。这可以通过领域知识(哪些语言节点在多态时开销较大)或通过实验(测量包含/排除特定节点/专用化的效果)来完成。为了帮助语言开发者更好地理解报告多元专用化的影响,提供了一些工具支持。

追踪单个拆分

在执行您的访客语言代码时,将--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.addTo中的WritePropertyNode。由于此调用目标只有一个已知的调用者,您可以分析其在调用树中的父级(即调用者)。在此示例中,这是HandlerTask.run,同样适用于它,从而引出TaskControlBlock.run,依此类推引出Scheduler.scheduleScheduler.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。由于它只有一个调用者,您会查看该调用者(Scheduler.queue),并且由于所有必要条件似乎都已满足,您将其标记为“需要拆分”。

将多元专用化转储到 IGV

在阅读本节之前,请考虑阅读拆分指南,因为转储的数据与拆分的工作原理直接相关。

在执行您的访客语言代码时,将--engine.SplittingDumpDecisions参数添加到命令行,将每次调用目标被标记为“需要拆分”时,转储一个图,显示以调用Node#reportPolymorphicSpecialize的节点结尾的节点链(通过子连接以及直接调用节点到被调用根节点链接)。

联系我们