使用 V8 的基于采样的分析器

V8 内置了基于采样的分析功能。分析默认情况下是关闭的,但可以通过 --prof 命令行选项启用。采样器记录 JavaScript 和 C/C++ 代码的堆栈。

构建 #

按照 使用 GN 构建 中的说明构建 d8 shell。

命令行 #

要开始分析,请使用 --prof 选项。在分析时,V8 会生成一个包含分析数据的 v8.log 文件。

Windows

build\Release\d8 --prof script.js

其他平台(如果您想分析 x64 构建,请将 ia32 替换为 x64

out/ia32.release/d8 --prof script.js

处理生成的输出 #

日志文件处理是使用 d8 shell 运行的 JS 脚本完成的。为了使此功能正常工作,d8 二进制文件(或符号链接,或 Windows 上的 d8.exe)必须位于 V8 签出的根目录中,或者位于环境变量 D8_PATH 指定的路径中。注意:此二进制文件仅用于处理日志,而不是用于实际分析,因此它是什么版本等并不重要。

确保用于分析的 d8 不是使用 is_component_build 构建的!

Windows

tools\windows-tick-processor.bat v8.log

Linux

tools/linux-tick-processor v8.log

macOS

tools/mac-tick-processor v8.log

--prof 的 Web UI #

使用 --preprocess 预处理日志(以解析 C++ 符号等)。

$V8_PATH/tools/linux-tick-processor --preprocess > v8.json

在浏览器中打开 tools/profview/index.html 并选择那里的 v8.json 文件。

示例输出 #

Statistical profiling result from benchmarks\v8.log, (4192 ticks, 0 unaccounted, 0 excluded).

 [Shared libraries]:
   ticks  total  nonlib   name
      9    0.2%    0.0%  C:\WINDOWS\system32\ntdll.dll
      2    0.0%    0.0%  C:\WINDOWS\system32\kernel32.dll

 [JavaScript]:
   ticks  total  nonlib   name
    741   17.7%   17.7%  LazyCompile: am3 crypto.js:108
    113    2.7%    2.7%  LazyCompile: Scheduler.schedule richards.js:188
    103    2.5%    2.5%  LazyCompile: rewrite_nboyer earley-boyer.js:3604
    103    2.5%    2.5%  LazyCompile: TaskControlBlock.run richards.js:324
     96    2.3%    2.3%  Builtin: JSConstructCall
    ...

 [C++]:
   ticks  total  nonlib   name
     94    2.2%    2.2%  v8::internal::ScavengeVisitor::VisitPointers
     33    0.8%    0.8%  v8::internal::SweepSpace
     32    0.8%    0.8%  v8::internal::Heap::MigrateObject
     30    0.7%    0.7%  v8::internal::Heap::AllocateArgumentsObject
    ...


 [GC]:
   ticks  total  nonlib   name
    458   10.9%

 [Bottom up (heavy) profile]:
  Note: percentage shows a share of a particular caller in the total
  amount of its parent calls.
  Callers occupying less than 2.0% are not shown.

   ticks parent  name
    741   17.7%  LazyCompile: am3 crypto.js:108
    449   60.6%    LazyCompile: montReduce crypto.js:583
    393   87.5%      LazyCompile: montSqrTo crypto.js:603
    212   53.9%        LazyCompile: bnpExp crypto.js:621
    212  100.0%          LazyCompile: bnModPowInt crypto.js:634
    212  100.0%            LazyCompile: RSADoPublic crypto.js:1521
    181   46.1%        LazyCompile: bnModPow crypto.js:1098
    181  100.0%          LazyCompile: RSADoPrivate crypto.js:1628
    ...

分析 Web 应用程序 #

如今高度优化的虚拟机可以以惊人的速度运行 Web 应用程序。但人们不应仅仅依靠它们来实现出色的性能:经过精心优化的算法或更便宜的函数通常可以在所有浏览器上实现多倍的速度提升。 Chrome DevToolsCPU 分析器 可以帮助您分析代码的瓶颈。但有时,您需要更深入、更细粒度地分析:这就是 V8 的内部分析器派上用场的地方。

让我们使用该分析器来检查 Mandelbrot 探索器演示,该演示由微软与 IE10 一起 发布。在演示发布后,V8 修复了一个导致计算速度过慢的错误(因此演示博客文章中 Chrome 的性能不佳),并进一步优化了引擎,实现了比标准系统库提供的更快的 exp() 近似值。在这些更改之后,**演示在 Chrome 中的运行速度比以前快了 8 倍**。

但是,如果您希望代码在所有浏览器上运行得更快怎么办?您应该首先**了解是什么让您的 CPU 繁忙**。使用以下命令行开关运行 Chrome(Windows 和 Linux Canary),这会导致它为您指定的 URL(在本例中是 Mandelbrot 演示的本地版本,没有 Web 工作者)输出分析器滴答信息(在 v8.log 文件中)。

./chrome --js-flags='--prof' --no-sandbox 'http://localhost:8080/'

在准备测试用例时,确保它在加载后立即开始工作,并在计算完成后关闭 Chrome(按 Alt+F4),这样您在日志文件中只有您关心的滴答声。还要注意,Web 工作者尚未通过此技术正确分析。

然后,使用 V8 附带的 tick-processor 脚本(或新的实用 Web 版本)处理 v8.log 文件。

v8/tools/linux-tick-processor v8.log

以下是处理后的输出中一个有趣的片段,应该会引起您的注意。

Statistical profiling result from null, (14306 ticks, 0 unaccounted, 0 excluded).
 [Shared libraries]:
   ticks  total  nonlib   name
   6326   44.2%    0.0%  /lib/x86_64-linux-gnu/libm-2.15.so
   3258   22.8%    0.0%  /.../chrome/src/out/Release/lib/libv8.so
   1411    9.9%    0.0%  /lib/x86_64-linux-gnu/libpthread-2.15.so
     27    0.2%    0.0%  /.../chrome/src/out/Release/lib/libwebkit.so

顶部部分显示 V8 在特定于操作系统的系统库中花费的时间比在其自己的代码中花费的时间更多。让我们通过检查“自下而上”输出部分来查看是什么导致了这种情况,您可以在其中将缩进的行读作“被调用者”(以 * 开头的行表示该函数已由 TurboFan 优化)。

[Bottom up (heavy) profile]:
  Note: percentage shows a share of a particular caller in the total
  amount of its parent calls.
  Callers occupying less than 2.0% are not shown.

   ticks parent  name
   6326   44.2%  /lib/x86_64-linux-gnu/libm-2.15.so
   6325  100.0%    LazyCompile: *exp native math.js:91
   6314   99.8%      LazyCompile: *calculateMandelbrot http://localhost:8080/Demo.js:215

超过**44% 的总时间花在执行系统库中的 exp() 函数上**!加上调用系统库的一些开销,这意味着大约三分之二的总时间花在评估 Math.exp() 上。

如果您查看 JavaScript 代码,您会发现 exp() 仅用于生成平滑的灰度调色板。有无数种方法可以生成平滑的灰度调色板,但假设您真的非常喜欢指数渐变。这就是算法优化发挥作用的地方。

您会注意到 exp() 的调用参数在 -4 < x < 0 范围内,因此我们可以安全地用该范围内的 泰勒级数近似值 替换它,该近似值仅通过一次乘法和几次除法即可提供相同的平滑渐变。

exp(x) ≈ 1 / ( 1 - x + x * x / 2) for -4 < x < 0

通过这种方式调整算法,与最新的 Canary 相比,性能提高了 30%,与 Chrome Canary 上基于系统库的 Math.exp() 相比,性能提高了 5 倍。

此示例展示了 V8 的内部分析器如何帮助您更深入地了解代码瓶颈,以及更智能的算法如何进一步提升性能。

要了解有关代表当今复杂且要求苛刻的 Web 应用程序的基准测试的更多信息,请阅读 V8 如何衡量实际性能