使用 D8 分析 javascript 如何被 V8 引擎优化的

在上一篇文章中咱们讲了如何使用 GN 编译 V8 源码,文章最后编译完成的可执行文件并非 V8,而是 D8。这篇咱们讲一下如何使用 D8 调试 javascript 代码。javascript

若是没有 d8,可使用 node 代替。html

新建文件 add-of-ints.js,输入如下内容:html5

function add(obj) {
    return obj.prop + obj.prop;
}

const length = 1000 * 1000;

const o = { prop: 1 };

for (let i = 0; i < length; i++) {
    add(o);

}

运行:java

d8 --trace-opt-verbose add-of-ints.js
或
node --trace-opt-verbose add-of-ints.js

输出结果为:node

从输出结果咱们能够看到 add 函数被编译器优化了,而且解释了优化的缘由。ICs 是 inline caches 的缩写,内联缓存是一种很常见的优化技术,这段简短的代码被 V8 引擎优化了两次,可是缘由却不一样。git

  • 第一次优化的缘由是 small function,add 函数是小函数,为了减少函数调用的开销,V8 引擎对 add 作了优化。github

  • 第二次的缘由是 hot and stable,我在知乎另外一个问题中曾说过,V8 有两个编译器,一个通用编译器,负责将 javascript 代码编译为机器码,另外一个是优化编译器。从上面的输出能够看出 V8 使用的优化编译器引擎是 Crankshaft。Crankshaft 负责找出常常被调用的代码,作内联缓存优化,后面的信息进一步说明了这个状况:ICs with typeinfo: 7/7 (100%), generic ICs: 0/7 (0%)。编程

在此再纠正以前的 2 个问题。缓存

一个是 V8 没有解释器,只有编译器,代码是直接编译成机器吗执行的,这是以前的 V8,而网络上关于 V8 的文章也大多比较老旧。这几天为了阅读 V8 源码查看了网上不少关于 V8 的论文和文章,发现 V8 已经引进了解释器。由于 V8 不单单能够优化代码,还能够去优化(deopt),引入解释器能够省去一些代码的重编译时间,另外一个缘由是解释器不单单能够解释 javascript 代码,还能够解释 asm 或者其余二进制中间码。网络

另外一个错误就是关于 V8 优化的,以前写过 JavaScript 函数式编程存在性能问题么? 中道:

永远不可能被优化的有:

  • Functions that contain a debugger statement

  • Functions that call literally eval()

  • Functions that contain a with statement

这个也是以前的文章,是以 Crankshaft 引擎为标准得出的结论。而 V8 已经开发了新的优化引擎——TurboFan

咱们再建立另外一个文件 add-of-mixed.js,输入:

// flag: --trace-opt-verbose

function add(obj) {
    return obj.prop + obj.prop;
}

var length = 1000 * 1000;

var objs = new Array(length);

var i = 0;

for (i = 0; i < length; i++) {
    objs[i] = Math.random();
}

var a = { prop: 'a' };
var b = { prop: 1 };

for (i = 0; i < length; i++) {
    add(objs[i] > 0.5 ? a : b);

}

运行:

d8 --trace-opt-verbose add-of-mixed.js
或
node --trace-opt-verbose add-of-mixed.js

输出结果为:

能够看到这段代码能不能作内联缓存优化全看 RP(人品) 了。

咱们再使用 --trace-opt --trace-deopt 参数看看 V8 引擎如何去优化

新建文件 add-of-mixed-dep.js,输入:

// flags: --trace-opt --trace-deopt

function add(obj) {
    return obj.prop + obj.prop;
}

var length = 10000;
var i = 0;
var a = { prop: 'a' };
var b = { prop: 1 };

for (i = 0; i < length; i++) {
    add(i !== 8000 ? a : b);

}

运行:

d8 --trace-opt --trace-deopt add-of-mixed-dep.js
或
node --trace-opt --trace-deopt add-of-mixed-dep.js

结果为:

V8 引擎内部使用 Hidden Classes 来表示 Object,关于 Hidden Classes 的文章已经不少了,我就不累述了。

运行 d8 --help 能够查看全部的 d8 命令行参数。若是使用 node,直接运行 node --help 输出的是 node 的命令行参数,若是想查看 V8 的,须要使用 node --v8-options

后面章节会介绍 V8 的 GC(命令行参数 --trace-gc)以及最有意思的 --allow-natives-syntax

推荐阅读一下 V8 的 bailout-reason.h 源码,这是一个 C++ 的头文件,里面几乎没有任何代码逻辑,定义了全部 javascript 代码不能被 V8 引擎优化的缘由,好比:

"Array index constant value too big"
"eval"
"ForOfStatement"
"Too many parameters"
"WithStatement"
……

后面章节介绍的 --allow-natives-syntax 相关 C++ 头文件是 runtime.h,经过 --allow-natives-syntax 参数能够在 javascript 中使用 V8 的运行时函数。咱们在以前的文章中已经使用过了,例如 HasFastProperties

参考文章:

相关文章
相关标签/搜索