Indicium:V8运行时跟踪工具

551 阅读7分钟

过去的三个月对我来说是一次很棒的学习经历,因为我作为实习生加入了V8团队(谷歌伦敦),并一直在开发一种*叫做Ind imium*的新工具。

这个系统分析器是一个统一的网络界面,用于跟踪、调试和分析内联缓存和地图在现实应用中是如何创建和修改的模式。

V8已经为集成电路地图提供了跟踪基础设施,可以使用集成电路资源管理器处理和分析集成电路事件,并使用地图处理器处理和分析地图事件。然而,以前的工具不允许我们全面分析地图和集成电路,现在系统分析器可以做到这一点。

image

信息

案例研究#

让我们通过一个例子来演示如何使用标记来分析V8中的地图和集成电路日志事件。

class Point {  constructor(x, y) {    if (x < 0 || y < 0) {      this.isNegative = true;    }    this.x = x;    this.y = y;  }  dotProduct(other) {    return this.x * other.x + this.y * other.y;  }}let a = new Point(1, 1);let b = new Point(2, 2);let dotProduct;// warmupfor (let i = 0; i < 10e5; i++) {  dotProduct = a.dotProduct(b);}console.time('snippet1');for (let i = 0; i < 10e6; i++) {  dotProduct = a.dotProduct(b);}console.timeEnd('snippet1');a = new Point(-1, -1);b = new Point(-2, -2);console.time('snippet2');for (let i = 0; i < 10e6; i++) {  dotProduct = a.dotProduct(b);}console.timeEnd('snippet2');

在这里,我们有一个Point类,它存储两个坐标和一个基于坐标值的额外布尔值。Point类有一个dot Product方法,它返回传递对象和接收器之间的点积。

为了让解释程序更容易,让我们把程序分成两个片段(忽略预热阶段):

片段1#

let a = new Point(1, 1);let b = new Point(2, 2);let dotProduct;console.time('snippet1');for (let i = 0; i < 10e6; i++) {  dotProduct = a.dotProduct(b);}console.timeEnd('snippet1');

片段2#

a = new Point(-1, -1);b = new Point(-2, -2);console.time('snippet2');for (let i = 0; i < 10e6; i++) {  dotProduct = a.dotProduct(b);}console.timeEnd('snippet2');

一旦我们运行程序,我们会注意到性能回归。即使我们正在测量两个类似片段的性能;通过在for-循环中调用dot Product函数来访问Point对象实例的属性x和y。

片段1的运行速度大约是片段2的3倍。唯一的区别是,我们在片段2中的Point对象中使用了x和y属性的负值。

image

片段的性能分析。

为了分析这种性能差异,我们可以使用V8附带的各种日志选项。这就是系统分析器闪耀的地方。它可以显示日志事件,并将它们与地图事件链接在一起,让我们探索隐藏在V8中的魔力。

在深入案例研究之前,让我们熟悉系统分析器工具的面板。该工具有四个主要面板:

  • 一个时间线小组来分析跨时间的Map/ICs事件,
  • 一个地图面板来可视化地图的过渡树,
  • 集成电路面板来获取集成电路事件的统计数据,
  • 显示脚本上Map/IC文件位置的源面板。

系统分析仪概述

按函数名对IC事件进行分组,以获取与dot Product关联的IC事件的详细信息。

我们正在分析函数dot Product是如何导致这种性能差异的。因此,我们将IC事件按Function Name分组,以获取有关与dot Product函数关联的IC事件的更深入信息。

我们注意到的第一件事是,我们有两个不同的IC状态转换记录的IC事件在这个函数。一个从未初始化到单态,另一个从单态到多态。多态IC状态表示现在我们正在跟踪多个与Point对象相关联的Map,这种多态状态更糟糕,因为我们必须执行额外的检查。

我们想知道为什么我们要为同一类型的对象创建多个Map形状。为此,我们切换有关IC状态的信息按钮,以获取有关映射地址从未初始化到单态的更多信息。

image

映射过渡树与单态IC相关联。

image

所述映射过渡树与多态性IC状态相关联。

对于单态IC状态,我们可以可视化过渡树,并看到我们只动态添加两个属性x和y,但是当涉及到多态性IC状态时,我们有一个包含三个属性is Negative,x和y的新Map。

image

地图面板传达文件位置信息,以突出显示源面板上的文件位置。

我们单击Map面板的文件位置部分,查看此is Negative属性在源代码中的添加位置,并可以使用此见解解决性能回归问题。

所以现在的问题是,我们如何通过使用我们从工具中生成的洞察力来解决性能回归

最小的解决方案是始终初始化is Negative属性。一般来说,合理的建议是所有实例属性都应该在构造函数中初始化。

更新后的Point类如下所示:

class Point {  constructor(x, y) {    this.isNegative = x < 0 || y < 0;    this.x = x;    this.y = y;  }  dotProduct(other) {    return this.x * other.x + this.y * other.y;  }}

如果我们再次使用修改后的Point类执行脚本,我们会看到在案例研究开始时定义的两个片段的执行执行非常相似。

在更新的跟踪中,我们看到多态集成电路状态被避免了,因为我们没有为同一类型的对象创建多个映射。

image

修改后的Point对象的映射过渡树。

系统分析仪#

现在让我们深入了解系统分析器中存在的不同面板。

时间轴面板#

时间线面板允许在时间上进行选择,这使得跨离散时间点或选定时间范围的IC/map状态可视化。它支持筛选功能,如缩放in/out选定时间范围的日志事件。

时间表面板概述

image

时间线面板概述(续)

地图面板#

地图面板有两个子面板:

  1. 地图详情
  2. 地图转换

地图面板可视化所选地图的过渡树。所选地图的元数据通过地图细节子面板显示。可以使用提供的接口搜索与地图地址相关的特定过渡树。从地图转换子面板上方的统计子面板,我们可以看到导致地图转换的属性和地图事件类型的统计数据。

地图面板概述

image

统计面板概述

IC面板#

IC面板显示有关特定时间范围内IC事件的统计信息,这些信息通过Timeline面板进行筛选。此外,IC面板允许根据各种选项(类型、类别、地图、文件位置)对IC事件进行分组。从分组选项中,地图和文件位置分组选项分别与地图和源代码面板交互,以显示地图的过渡树,并突出显示与集成电路事件相关的文件位置。

image

IC面板概述

image

IC面板概述(续)

image

IC面板概述(续)

image

IC面板概述(续)

源面板#

源面板显示加载的脚本,并带有可点击的标记,以发出自定义事件,该事件在自定义面板上选择地图和集成电路日志事件。可以从向下钻取栏中选择加载的脚本。从地图面板和集成电路面板选择文件位置会突出显示源代码面板上选定的文件位置。

image

源面板概述

致谢#

我要感谢V8和网络安卓团队的每一个人,特别是我的主持人Sathya和共同主持人Camillo,感谢他们在我实习期间对我的支持,并给我机会从事如此酷的项目。

我在谷歌实习了一个很棒的夏天!