分析单个JavaScript组件产生的CPU负载

让我们谈谈如何观察应用程序JavaScript代码消耗了多少CPU资源。 同时,我建议围绕组件-应用程序的基本构建块进行对话。 通过这种方法,提高生产率的任何努力(或寻找导致程序变慢的原因的努力)都可以集中在(希望)项目的小而自给自足的部分。 同时,我假设您的前端应用程序与许多其他现代项目一样,是通过组装适合重复使用的接口小片段而创建的。 如果不是这样,那么我们的推理可以应用于其他应用程序,但是您将不得不找到自己的方式将大型代码划分为片段,并且您将需要考虑如何分析这些片段。



为什么需要这个?


为什么要通过JavaScript测量CPU消耗? 事实是,如今,应用程序性能通常与处理器的功能相关。 让我在对Planet Performance Podcast的采访中自由引用Steve Soders和Pat Minan的话。 双方都表示,应用程序性能不再局限于网络功能或网络延迟。 网络越来越快。 此外,开发人员学会了使用GZIP(或更确切地说,使用brotli)压缩服务器文本响应,并弄清楚如何优化图像。 一切都非常简单。

现代应用程序的性能瓶颈是处理器。 在移动环境中尤其如此。 同时,我们对现代Web应用程序的交互功能的期望也在增长。 我们希望此类应用程序的界面能够非常快速,流畅地工作。 所有这一切都需要越来越多的JavaScript代码。 此外,我们需要记住,1 MB的图像与1 MB的JavaScript不同。 图像是逐步下载的,此时该应用程序解决了其他问题。 但是JavaScript代码通常是这样的资源,没有它,应用程序将无法运行。 为了确保现代应用程序的功能,需要大量的JS代码,在真正起作用之前,必须对其进行解析和执行。 这些任务在很大程度上取决于处理器的功能。

绩效指标


我们将使用这样的代码片段速度指示器作为处理它们所需的处理器指令数量。 这将使我们能够将测量结果与特定计算机的属性以及测量时的状态分开。 基于时间的指标(如TTI)具有太多的“噪音”。 它们取决于网络连接的状态以及测量时计算机上发生的任何其他情况。 例如,某些在加载被调查页面期间执行的脚本,或忙于后台进程中某些内容的病毒,可能会影响时间性能指标。 关于浏览器扩展,也可以这样说,它会消耗大量系统资源并减慢页面速度。 另一方面,在计算处理器指令数量时,时间并不重要。 正如您很快就会看到的那样,这些指标可能是真正稳定的。

主意


这是我们工作的基础思想:我们需要创建一个“实验室”,在其中进行更改时将启动并检查代码。 “实验室”是指一台常规计算机,也许是您经常使用的计算机。 版本控制系统为我们提供了处理钩子,您可以使用它们来拦截某些事件并执行某些检查。 当然,可以在提交后在“实验室”中进行测量。 但是您可能知道,对已提交到提交阶段的代码的更改将比对正在编写的代码的更改(如果有的话)要慢得多。 修复产品的Beta代码以及修复投入生产的代码也是如此。

每次更改代码时,都需要在更改前后比较其性能。 为此,我们努力隔离地研究组件。 结果,我们将能够清楚地看到问题并能够准确知道问题的根源。

好消息是可以使用例如Puppeteer在真实的浏览器中进行此类研究。 这是一个无需使用Node.js用户界面即可控制浏览器的工具。

搜索代码以进行研究


为了找到用于研究的代码,我们可以参考任何样式指南或任何设计系统。 总的来说,我们对提供简短,孤立的组件使用示例的任何事情感到满意。

什么是“样式指南”? 通常,这是一个Web应用程序,演示开发人员可用的用户界面元素的所有组件或“构建块”。 它可以是某个第三方组件库,也可以是您自己创建的东西。

在Internet上搜索此类项目时,我在Twitter上遇到了一个最近的讨论线程 ,该线程讨论了相对较新的React组件库。 我看了那里提到的几个图书馆。

毫不奇怪,现代高质量库随文档一起提供,其中包括工作代码示例。 这是通过它们的方式实现的几个库和Button组件。 这些库的文档包含使用这些组件的示例。 我们正在谈论Chakra库和Semantic UI React库。


按钮组件Chakra文档


按钮语义UI React文档

这正是我们所需要的。 这些是我们可以检查其代码消耗的处理器资源的示例。 在文档的开头或以JSDoc样式编写的代码注释中可以找到类似的示例。 也许,如果幸运的话,您会发现这样的示例,这些示例被设计为单独的文件,例如以单元测试文件的形式。 肯定会的。 毕竟,我们都编写了单元测试。 对不对

档案


想象一下,为了演示所描述的性能分析方法,正在研究的库中有一个Button组件,其代码位于Button.js文件中,带有单元测试Button-test.js文件Button-test.js附加到该文件,以及包含使用该组件示例的文件- Button-example.js 。 我们需要创建一种可以在其中运行测试代码的环境中的测试页面。 类似于test.html

组成部分


这是一个简单的Button组件。 我在这里使用React,但是可以使用任何适合您的技术来编写您的组件。

 import React from 'react'; const Button = (props) =>  props.href    ? <a {...props} className="Button"/>    : <button {...props} className="Button"/> export default Button; 

例子


这是一个使用Button组件的示例。 如您所见,在这种情况下,有两个使用不同属性的组件选项。

 import React from 'react'; import Button from 'Button'; export default [  <Button onClick={() => alert('ouch')}>    Click me  </Button>,  <Button href="https://reactjs.com">    Follow me  </Button>, ] 

测验


这是可以加载任何组件的test.html页面。 请注意对performance对象的方法调用。 在他们的帮助下,我们应要求将其写入Chrome性能日志文件。 很快我们将使用这些记录。

 const examples =  await import(location.hash + '-example.js'); examples.forEach(example =>  performance.mark('my mark start');  ReactDOM.render(<div>{example}</div>, where);  performance.mark('my mark end');  performance.measure(    'my mark', 'my mark start', 'my mark end'); ); 

测试选手


为了在Chrome中加载测试页,我们可以使用Puppeteer Node.js库,该库使我们可以访问用于管理浏览器的API。 您可以在任何操作系统上使用此库。 它具有自己的Chrome副本,但也可以用于开发人员计算机上已经存在的各种版本的Chrome或Chromium实例。 可以启动Chrome,使其窗口不可见。 测试是自动执行的,而开发人员则无需查看浏览器窗口。 Chrome可以在正常模式下启动。 这对于调试目的很有用。

这是从命令行运行的示例Node.js脚本,该脚本加载测试页并将数据写入性能日志文件。 在浏览器中,在tracing.start()end()命令之间发生的所有事情都将写到trace.json文件中(我想非常详细地说明)。

 import pup from 'puppeteer'; const browser = await pup.launch(); const page = await browser.newPage(); await page.tracing.start({path: 'trace.json'}); await page.goto('test.html#Button'); await page.tracing.stop(); await browser.close(); 

开发人员可以通过指定跟踪的“类别”来管理性能数据的“详细信息”。 如果您通过Chrome浏览器访问chrome://tracing ,可以看到可用类别的列表chrome://tracing ,单击“ Record ,然后在出现的窗口中打开“ Edit categories部分。


配置写入性能日志的数据组成

结果


在使用Puppeteer检查了测试页之后,您可以通过以下浏览器访问chrome://tracing浏览器来分析性能测量的结果chrome://tracing并下载刚刚记录的trace.json文件。


Trace.json可视化

在这里,您可以看到调用performance.measure('my mark')方法的结果。 measure()调用仅用于调试目的,以防开发人员要打开trace.json文件并查看它。 页面上发生的所有事情都包含在my mark

这是trace.json


trace.json文件的片段

为了找出我们所需要的,从End标记的同一指示器中减去Start标记的处理器指令数( ticount )的指示器就足够了。 这使您可以找出需要多少处理器指令才能在浏览器中显示该组件。 该数字与您用来确定某个组件变快还是变慢的编号相同。

细节决定成败


现在,我们仅测量了表征单个组件页面的第一个输出的指标。 仅此而已。 必须测量与可以执行的最少代码量有关的指标。 这使您可以减少“噪音”的程度。 细节在于魔鬼。 测量的性能越小越好。 测量后,有必要从获得的结果中去除超出显影剂影响范围的物质。 例如,与垃圾收集操作有关的数据。 该组件不控制此类操作。 如果执行它们,则意味着浏览器在渲染组件的过程中决定自行启动它们。 结果,应该从最终结果中删除用于垃圾回收的处理器资源。

与垃圾回收相关的数据块(此“数据块”更正确地称为“事件”)称为V8.GCScavenger 。 应该从渲染组件的处理器指令数中减去其tidelta这是跟踪事件文档。 是的,它已经过时,并且不包含有关我们所需指标的信息:

  • tidelta处理事件所需的处理器指令数。
  • ticount启动事件的指令数。

您需要对我们要测量的内容非常小心。 浏览器是高度智能的实体。 他们优化了运行多次的代码。 在下一张图中,您可以看到输出特定组件所需的处理器指令数。 第一次渲染操作需要最多的资源。 随后的操作将大大减轻处理器的负担。 分析代码性能时应牢记这一点。


相同组件的10个渲染操作

这是另一个详细信息:如果组件执行某些异步操作(例如,它使用setTimeout()fetch() ),那么将不考虑由异步代码创建的系统上的负载。 也许很好。 也许不好 如果要研究此类组件的性能,请考虑单独研究异步代码。

强信号


如果您采取负责任的方法来解决要精确测量的问题,那么您将获得一个真正稳定的信号,该信号可以反映对任何更改的性能的影响。 我喜欢下一张图中线条的平滑度。


测量结果稳定

下图显示了React中一个简单的<span>元素的10次渲染操作的测量结果。 这些结果中没有其他内容。 事实证明,此操作需要2.15到220万个处理器指令。 如果将<span>包装在<p> ,则要输出这样的设计,您需要大约230万条指令。 这种准确性让我印象深刻。 如果开发人员可以看到将单个<p>元素添加到页面时出现的性能差异,则意味着开发人员可以使用非常强大的工具。

如何精确表示这种精度的度量取决于开发人员。 如果他不需要这种准确性,则可以随时测量较大片段的渲染性能。

其他性能信息


现在,开发人员可以使用其系统来找到非常准确地描述最小代码段性能的数字指标,开发人员可以使用该系统解决各种问题。 因此,使用performance.mark()您可以将其他有用的信息写入trace.json 。 您可以告诉开发团队的成员正在发生什么,什么原因导致执行某些代码所需的处理器指令数量增加。 您可以在性能报告中包括有关DOM节点数或React所执行的DOM写数的信息。 实际上,您可以在此处显示有关很多信息。 您可以计算页面布局重新计算的次数。 使用Puppeteer,您可以截取页面的屏幕截图,并比较进行更改前后界面的外观。 有时,显示页面所需的处理器指令数量的增加看起来完全不足为奇。 例如,如果将10个按钮和12个用于编辑和格式化文本的字段添加到该页面的新版本中。

总结


今天在这里讨论的每个人都可以使用它吗? 是的,你可以。 为此,您需要使用Chrome版本78或更高版本。 如果trace.json具有ticounttidelta ,则可以使用以上内容。 早期版本的Chrome不支持。

不幸的是,在Mac平台上无法获得有关处理器指令数量的信息。 我还没有尝试过Windows,所以我不能说任何有关此操作系统的信息。 总的来说-我们的朋友是Unix和Linux。

应当注意,为了使浏览器能够提供有关处理器指令的信息,您将需要使用几个标志-这些标志是--no-sandbox--enable-thread-instruction-count 。 将它们传递到Puppeteer启动的浏览器的方法如下:

 await puppeteer.launch({  args: [    '--no-sandbox',    '--enable-thread-instruction-count',  ]}); 

希望现在您可以将Web应用程序性能分析提高到一个新的水平。

亲爱的读者们! 您是否打算使用此方法来分析此处介绍的Web项目的性能?


Source: https://habr.com/ru/post/zh-CN479266/


All Articles