原视频
上一篇文章JavaScript 为什么快--第二篇讲到,抽象语法树(Abstract Syntax Tree,AST)生成字节码。本篇文章将延续字节码后的,JavaScript代码是如何执行的。
How JavaScript Engines Work by Franziska Hinkelmann @ Web Rebels 2017
结论先行
我们以前看到的优化建议,对V8而言,很可惜这些经验逐渐都没用了。
Avoid keywords(eval, try-catch, ...) not useful anymore.
经验之谈的优化点,对于日益精进的V8来说并不可靠,最可靠的优化是”静态类型“
Write code that looks "statically typed"
JavaScript引擎使用runtime时采集的类型信息,优化提速。所以虽然JavaScript虽然是动态类型的语言,但我们要尽可能的写静态类型的代码。
另外目前和热门的WebAssembly也使用TurboFan优化代码。
JavaScript引擎有哪些?
- 浏览器:V8, Chakra, JavaScriptCore, SpiderMonkey
- Node.js:V8, Chakra, SpiderNode
- Electron: V8
- IoT: Duktape, JerryScript
JavaScript背景
JavaScript实现EcmaScript标准,由TC39制定。正是这个标准,导致JavaScript无论是哪个JS引擎实现,都需要考虑一堆的类型判断。例如类型转化的定义:https://www.ecma-international.org/ecma-262/9.0/index.html#sec-type-conversion
JavaScript一个很吸引人的特性就是:动态类型语言(var定义变量),C++是静态类型语言。而JS引擎也是C++实现的,所以对于引擎开发者来说还是需要考虑将JS的变量转换为静态类型。
为了快速解析并运行JavaScript脚本,V8引入了JIT,快速解析出字节码,由基线编译器生成"粗糙"的机器码去执行,这样实现了快速执行。
但是后果就是这些基线代码效率很低,因为Ecma的标准中有大量的歧义和类型判定。
所以V8又引入了优化编译器,将经常执行的”hot“函数优化机器码提升,这些函数的执行效率。
注释:Just In Time(JIT) Compilation
Generate machine code during runtime, not ahead of time(AOT)
基线编译器
将字节码转化成基线机器码,去执行。并且将经常执行的”hot“函数,发给优化编译器,生成优化后的函数机器码提效。
优化编译器
re-compoile, 使用之前执行的类型信息优化,重新编译”hot“函数。(所以不要改变类型!!!)
de-compile, 一旦类型改变,就会失效,就会跌回baseline,重新优化。
优化点:永远构造相同类型的对象:
//举个栗子
function load(obj) {
return obj.x;
}
load({x:4, a: 1});
load({x:4, a: 2});
load({x:4, a: 3});
load({x:4, b: 1});//这里会触发de-compile
//我们应该这样使用:
load({x: 4, a: 7, b: undefined, c: undefined});
load({x: 4, a: undefined, b: 1, c: undefined});
load({x: 4, a: undefined, b: undefined, c: 3});
//举个栗子
function add(obj) {
return 1+obj.x;//JavaScript 中"+"号的复杂定义;obj.x的Property lookup
}
add({x:7});//+ operator according to spec(slow)
add({x:42});
...
add({x:123});//"hot",they "always integers",fast code for integer addition(fast);
add({x:'Hello;}); //"hot" failed, + operator according to spec(slow)
V8中的类型比较:HaveSameMap
Shape of object = map = hidden class
var obj1 = {x:5};
var obj2 = {x:17};
console.log(%HaveSameMap(obj1, obj2));
可以用V8参数开启这个内置C函数来检查2个Object是否一致:$ node --allow-natives-syntax maps.js
。