向运行时报告多态特化

本指南概述了语言实现者为了利用单态化(分割)策略而需要做些什么。有关其工作原理的更多信息,请参阅 分割 指南。

简而言之,单态化启发式算法依赖于语言报告每个节点的多态特化,这些节点可能通过分割返回到单态状态。在此上下文中,多态特化是指任何节点重写,导致节点更改其“多态程度”。这包括但不限于激活另一个特化、增加活动特化实例的数量、排除特化等等。

手动报告多态特化 #

为了便于报告多态特化,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 基准测试的一部分输出。

...
[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 的节点结束的节点链(通过子连接以及直接调用节点到被调用者根节点的链接连接)。

联系我们