返回 登录
0

JavaScript性能故事:直觉工程和Chrome堆配置文件

阅读2998

原文:A Tale of JavaScript Performance
作者:Tom Lagier
翻译:Vincent

译者注:本文记录了作者对JavaScript性能的研究,以及遵循直觉工程的原则,将这些研究成果以何种方式进行展示,能够更直观地让用户了解。以下为译文。

本系列文章的目的是通过我正在进行的工作,即为Chrome内存配置文件创建可视化工具,来记录我对JavaScript性能的探索。我们将从当时创建这个项目的目的开始讲起,然后深入了解Chrome用于表示内存堆的文件格式。

直觉工程

数月前,我曾有机会去聆听了Casey Rosenthal一场关于直觉工程主题的精彩的演讲。直觉工程是一种概念,这种概念是指人们在理解信息时变得更容易,而不需要再去仔细思考。像视觉、声音和气味这样的东西,可以用阅读一段文字或者解释一个表格这么一小段时间来处理。

你能多快地了解这个应用服务器上发生了什么:



AWS CloudWatch日志监控器

那相比这个呢:


新Relic错误分析展示图

很显然,在适当的抽象层面上对数据进行可视化表示可以更容易地了解实际发生的情况。Casey说一旦你接触过这种抽象有一段时间,那么你就会形成一种直觉意识:“什么是正常的”。基线的变化是显而易见的,这样我们就可以利用人类的自然本能来进行模式识别,从而让我们深入了解可能导致行为变化的因素。

这使得直觉工程成为设计诊断接口的绝佳平台,特别是那些随着时间而显示数据的平台。通过提供一个允许用户开发直观基准的接口,您可以让用户快速区分每条基准线之间的区别。

项目就此诞生

对直觉工程有一个大概了解之后,我很想在个人项目上尝试一下。当时我正在研究一些JavaScript内存堆分析,试图诊断应用程序占用内存的原因,并追踪一些内存泄漏。

花费几天时间研究接口之后:


Chrome内存文件监控器

我想也许我可以利用这个直觉工程的想法来构建一个更好的堆快照视图。通过以可视化的方式呈现数据,我们可以快速了解内存分配的位置以及GC运行期间没有清理的内容。在应用程序中开发直观的基准可能很困难,但是随着时间的推移,应用程序当然可以有一个视觉上的理解,可以让我们快速识别重大事件或问题领域。

解码堆配置文件格式

如果我想要以可视化的方式表示堆配置文件,我的第一步需要将导出的Chrome堆概要文件格式解析为可以看到的东西。堆配置文件格式巧妙地压缩了JSON。它看起来像这样:


摘录自.heapprofile文件

这些文件非常大,通常是几百兆字节。这种格式花了我很长一段时间才搞清楚,但是我最终通过Chrome devtools进行了大量的试用和错误的方法,终于解决了这个问题。

内存表示为图形,节点表示内存分配,edge表示对这些内存位置的引用。 样本由时间戳和最后分配的edge组成。 该文件的每行代表其类型的一个对象。 对象的字段在元数据中标识为node_fields。

一个节点可以通过其edge_count字段来确定它的边。从第一个节点开始,edges递增地分配给节点。因此,第一个节点的edge_count为8,由前8个edge引用。 第二个节点edge_count为17,拥有接下来的17个edges,依此类推。

对父节点的edges引用不是由ID完成的,而是通过数组中节点的起始元素的索引来完成。 类似地,时间样本由样本中分配的最后一个edges的结束索引标记。 我花了很长时间才弄明白这一点,但好处是显而易见的——它创建了一个唯一的整数键,用于快速查找数组。不需要将整个图解析为数据结构,以便确定有关单个edges或节点的数据。

关于节点

对于任何给定的节点,有三个统计特别重要:

  1. 自身大小-仅由节点本身单独存储的内存大小。 非常大的内存中对象将具有较大的自身大小,比如长字符串、字典和具有大主体的函数。寻找具有非常大的自身大小的对象通常是减少应用程序内存占用的第一步。

  2. 保留大小-对象的自身大小加上所有被释放的对象的大小应该被删除。 在数学上,这个节点主导的任何节点(更多的是在一秒钟内)被添加到其保留的大小。 具有非常大的保留尺寸的对象如果被清理,将释放大量内存,即使它们没有特别大的自身尺寸。

  3. 其保留者的身份 - 节点的任何边缘都提供了将内容保存在内存中的信息。 一旦你确定了一组呈现问题的节点,这是最重要的,因为它表明在内存中保留这些节点是什么。 保留者还可以为您提供关于标签不良节点的真实性质的有用线索。

第一个和第三个方法的作用是微不足道的-因为节点自身的大小是在定义节点时给出的,通过构建图形,您可以轻松找到节点的保留者。 然而,计算节点的保留大小并不是那么简单。 要计算它,我们必须首先拥有节点占主导地位的所有节点的列表。

建立支配树

如果遍历到根节点时,所有路径必须经过N,就认为节点就被节点N所主导。站在内存的角度,这就意味着如果你删除了从N到节点的引用,那么它就不会再有引用了,在下一次传递中会被垃圾回收站给回收掉。为了从图中生成一系列的支配节点,我们就需要创建支配树了。

有一种算法很容易就可以构建这样一种树,称为Tarjan-Lengauer算法。这个算法是相当有技术性的,我开始撸起袖子,还有很多疑问,就尝试用JavaScript去实现。我思考了一下并且稍微挖掘了一下,然后我就意识到,在我面前的是一个多么好的已经实现的开源算法。

OSS万岁

我知道Chrome devtools正在构建一个支配树,因为它们从文件中加载堆快照时,可以轻松地将其作为进程消息之一进行闪烁。快速地访问Chromium源代码后,让我想到了HeapSnapshot.js和朋友。我意识到——如果Chrome有一个开源的、可以用于生产的应用程序,那么我为什么还要自己去弄呢?

我现在很高兴地跟大家说,它运行得很好。我能够提取HeapSnapshot模块并在浏览器中进行旋转。我必须填补一些东西,但是devtools模块的目的是用来挂起一个全局对象,这样就可以轻易地进行抽取—只需提供您自己的、正确命名的全局对象,确保它们拥有所需的任何实用程序,并且它们将完成其余的工作。。

稍后,我还可以对它进行一些临时的操作,然后我就可以为它提供一个堆配置文件,并在最后得到一个完全膨胀的堆表示。我有了我的数据,准备好了,准备好了我想要的任何可视化方法!

评论