文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

面试写:说说执行 JavaScript 的 V8 引擎做了什么?

2024-12-13 22:05

关注

V8 引擎是由 Google 用 C++ 开源的 JavaScript 与 WebAssembly 引擎,目前像是 Chrome 和 Node.js 都是使用 V8 在执行 JavaScript。除了 V8 以外还有 SpiderMonkey(最早的 JavaScript 引擎,目前是 Firefox 浏览器在使用)与 JavaScriptCore(Safari 浏览器使用)等其他 JavaScript 引擎。

好的,那麽 V8 引擎到底是如何执行 JavaScript 的呢?

V8 引擎执行流程

Scanner

V8 引擎取得 JavaScript 源代码后的第一步,就是让 Parser 使用 Scanner​ 提供的 Tokens(Tokens 裡有 JavaScript 内的语法关键字,像是 function、async、if 等),将 JavaScript 的原始码解析成** abstract syntax tree**,就是大家常在相关文章中看到的 AST(抽象语法树)。

如果好奇 AST 长什麽样子的话,可以使用 acron 这个 JavaScript Parser,或是 这个网站 生成 AST 参考看看。以下是使用 acron 的代码:

const { Parser } = require('acorn')

const javascriptCode = `
let name;
name = 'Clark';
`;

const ast = Parser.parse(javascriptCode, { ecmaVersion: 2020 });
console.log(JSON.stringify(ast));

下方是解析 let name; name = 'Clark'; 所得到的 AST:

{
"type": "Program",
"start": 0,
"end": 31,
"body": [
{
"type": "VariableDeclaration",
"start": 3,
"end": 12,
"declarations": [
{
"type": "VariableDeclarator",
"start": 7,
"end": 11,
"id": {
"type": "Identifier",
"start": 7,
"end": 11,
"name": "name"
},
"init": null
}
],
"kind": "let"
},
{
"type": "ExpressionStatement",
"start": 15,
"end": 30,
"expression": {
"type": "AssignmentExpression",
"start": 15,
"end": 29,
"operator": "=",
"left": {
"type": "Identifier",
"start": 15,
"end": 19,
"name": "name"
},
"right": {
"type": "Literal",
"start": 22,
"end": 29,
"value": "Clark",
"raw": "'Clark'"
}
}
}
],
"sourceType": "script"
}

如果再进一步,将上方的 AST 转化成图表,会长这样:

AST 可以从上到下,由左而右去理解它在执行的步骤:

产生 AST 后,就完成了 V8 引擎的第一个步骤。

JIT(Just-In-Time)

JIT 的中文名称是即时编译,这也是 V8 引擎所採用在执行时编译 JavaScript 的方式。

将代码转变为可执行的语言有几种方法,第一种是编译语言,像是 C/C++ 在写完代码的时候,会先经过编译器将代码变成机器码才能执行。第二种就像 JavaScript,会在执行的时候将代码解释成机器懂的语言,一边解释边执行的这种,称作直译语言。

编译语言的好处是可以在执行前的编译阶段,审视所有的代码,将可以做的优化都完成,但直译语言就无法做到这一点,因为执行时才开始解释的关係,执行上就相对较慢,也没办法在一开始做优化,为了处理这个状况,JIT 出现了。

JIT 结合解释和编译两者,让执行 JavaScript 的时候,能够分析代码执行过程的情报,并在取得足够情报时,将相关的代码再编译成效能更快的机器码。

听起来 JIT 超讚,而在 V8 引擎裡负责处理 JIT 的左右手分别为 Ignition 和 **TurboFan**。

Ignition & TurboFan

成功解析出 AST 后,Ignition​ 会将 AST​ 解释为 ByteCode​,成为可执行的语言,但是 V8 引擎还未在这裡结束,Ignition 用 ByteCode 执行的时候,会搜集代码在执行时的类型信息。举个例子,如果我们有个 sum​ 函式,并且始终确定呼叫的参数类型都是 number,那麽 Ignition 会将它记录起来。

此时,在另一方面的 TurboFan 就会去查看这些信息,当它确认到“只有 number​ 类型的参数会被送进 sum​ 这个函式执行”这个情报的时候,就会进行 Optimization,把 sum 从 ByteCode 再编译为更快的机器码执行。

如此一来,就能够保留 JavaScript 直译语言的特性,又能够在执行的时候优化性能。

但毕竟是 JavaScript,谁也不敢保证第一百万零一次送进来的参数仍然是 number​,因此当 sum 接收到的参数与之前 Optimization 的策略不同时,就会进行 Deoptimization 的动作。

TurboFan 的 Optimization 并不是将原有的 ByteCode 直接变成机器码,而是在产生机器码的同时,增加一个 Checkpoint 到 ByteCode 和机器码之间,在执行机器码之前,会先用 Checkpoint 检查是否与先前 Optimization 的类型符合。这样的话,当 sum 以与 Optimization 不同的类型被呼叫的时候,就会在 Checkpoint 这关被挡下来,并进行 Deoptimization。

最后如果 TurboFan 重複执行了 5 次 Optimization 和 Deoptimization 的过程,就会直接放弃治疗,不会再帮这个函式做 Optimization。

那到底该怎麽知道 TurboFan 有没有真的做 Optimization 咧?我们可以用下方的代码来做个实验:

const loopCount = 10000000;
const sum = (a, b) => a + b;

performance.mark('first_start');

for (let i = 0; i < loopCount; i += 1) {
sum(1, i);
}

performance.mark('first_end');


performance.mark('second_start');

for (let i = 0; i < loopCount; i += 1) {
sum(1, i);
}

performance.mark('second_end');

performance.measure('first_measure', 'first_start', 'first_end');
const first_measures = performance.getEntriesByName('first_measure');
console.log(first_measures[0]);

performance.measure('second_measure', 'second_start', 'second_end');
const second_measures = performance.getEntriesByName('second_measure');
console.log(second_measures[0]);

上方利用 Node.js v18.1 的 perf_hooks 做执行速度的测量,执行结果如下:

执行后会发现第一次执行的时间花了 8 秒,第二次的执行时间只花了 6 秒,大家可以再把 loopCount 的数字改大一点,差距会越来越明显。

但是这麽做仍然没办法确认是 TurboFan 动了手脚,因此接下来执行的时候,加上 --trace-opt 的 flag,看看 Optimization 是否有发生:

执行后的信息显示了 TurboFan 做的几次 Optimization,也有把每次 Optimization 的原因写下来,像第一二行分别显示了原因为 hot and stable 和 small function,这些都是 TurboFan 背后做的 Optimization 策略。

那 Deoptimization 的部分呢?要测试也很简单,只要把第二个迴圈的参数型别改成 String 送给 sum 函式执行,那 TurboFan 就会进行 Deoptimization,为了查看 Deoptimization 的讯息,下方执行的时候再加上 --trace-deopt:

在 highlight 的那一段,就是因为送入 sum 的参数型别不同,所以执行了 Deoptimization,但是接下来又因为一直送 String 进 sum 执行,所以 TurboFan 又会再替 sum 重新做 Optimization。

总结

整理 V8 引擎执行 JavaScript 的过程后,能够得出下方的流程图:

搭配上图解说 V8 引擎如何执行 JavaScript:

作者:神Q超人 > 来源:medium

原文:https://medium.com/tarbugs/%E5%9F%B7%E8%A1%8C-javascript-%E7%9A%84-v8-%E5%BC%95%E6%93%8E%E5%81%9A%E4%BA%86%E4%BB%80%E9%BA%BC-f97e5b4b3fbe

作者:Andy Chen  

译者:前端小智

来源:medium 

原文:https://medium.com/starbugs/%E5%8E%9F%E4%BE%86%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%89%93%E5%8C%85%E4%B9%9F%E6%9C%89%E9%80%99%E9%BA%BC%E5%A4%9A%E7%9C%89%E8%A7%92-%E6%B7%BA%E8%AB%87-tree-shaking-%E6%A9%9F%E5%88%B6-8375d35d87b2​

来源:大迁世界内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯