JavaScript 为什么快--第二篇

简介: 上一篇,我们介绍了 V8 引擎的执行管道架构。本篇将着重介绍 V8 的语法解析过程。原视频上一篇是产品经理思维;本篇则是理工科思维;语法解析阶段对于前端来说尤其重要,相对 Noder 来说较弱,因为 parser 只会影响应用启动和前期的运行阶段。

上一篇,我们介绍了 V8 引擎的执行管道架构。本篇将着重介绍 V8 的语法解析过程。原视频
上一篇是产品经理思维;本篇则是理工科思维;
语法解析阶段对于前端来说尤其重要,相对 Noder 来说较弱,因为 parser 只会影响应用启动和前期的运行阶段。
对于前端同学来说,经常习惯性的引入一些很大的库,而只使用了其中1,2个函数。例如 lodash。这样对性能的影响到底有多大?

还是结论先行

  1. V8的语法解析有2种模式:eager 解析器(全面)和 lazy 预解析器(快速)。虽然 lazy 解析比 eager 快一倍,但是lazy可能导致需要1.5倍的解析时间;(lazy 预解析后,还需要 eager 解析一次)。你可以用Optimize.js强制 eager 运行
  2. JavaScript 的语法解析速度为:1MB/S。解析400k JavaScript,需要大概370ms。可以通过 chrome 浏览器地址栏 chrome://tracing 查看具体时间;
  3. 前端页面运行的 JavaScript 代码尽量少;解析器也有缓存,缓存字节码,如果采用 bundle 的话,更新 bundle 会导致整个 bundle 失效。

计算机编译原理简单介绍

由于本篇需要部分计算机编译原理背景知识。所以感觉需要补充一下,计算机编译原理,将人能读懂的代码转换成机器能读懂的代码,机器执行时只认识机器语言指令。
通常计算机高级语言都需要经过:源程序->语法解析->中间代码生成->代码优化->目标代码生成->目标程序。对应V8也不例外:JS源代码->语法解析->生成字节码->编译器->转化器->运行代码。
语法解析阶段生成语法树和作用域,就是将我们的每行代码变成语法树状结构,来消除歧义,代码分析,绑定作用域。
可以通过esprima看看JavaScript的语法树什么样子:http://esprima.org/demo/parse.html#

本篇主要介绍V8的语法解析过程,产物就是字节码(中间代码)。下一篇介绍V8的编译器运行。

JavaScript 语法解析 - lazy 要比 eager 好吗?

什么是 JavaScript 语法解析?

我们从上一篇的 JavaScript 执行管道,下图红色的部分就是语法解析的过程。实际就是 JavaScript 的编译阶段。虽然编译过程不参与“ JavaScript 的运行阶段(下图蓝色部分)”,但作为动态脚本语言,JavaScript 的解析在代码变更和实际运行时,还是会触发语法解析的。

15335656973482

我们为何要关心解析?

  • 一个典型的单页 Web 应用:

    • 需要加载0.4MB的 JavaScript;
    • 大约耗时370毫秒;(在手机型号 Moto G4 测试)
  • ->语法解析的速度 ~ 1MB/s

V8 是如何处理 JavaScript 语法解析的? eager parse & lazy parse

这是 V8 的自己实现,为了提升 JavaScript 文件的语法解析速度;目前非 JavaScript 引擎的官方规范。

  • 2种解析模式: eager (全面解析模式) 和 lazy (快速解析模式)
  • 为什么解析 JavaScript 代码那么难?

2种解析器

  • 解析器: 全面解析模式, "eager"

    • 用于解析我们想编译的函数;
    • 构建语法树;
    • 构建函数作用域(Scopes);
    • 找出所有语法错误;
  • 预-解析器: 快速解析模式, "lazy"

    • 用于跳过我们不想编译的函数们;
    • 不构建语法树,会构建函数作用域,但不设置函数作用域中的变量引用(variable references)和变量申明(variable declarations);
    • 解析速度,大约比eager解析器快2倍
    • 找出限定的几种错误(没有遵守JavaScript的规范)

Lazy or eager?

lazy 预编译由前2位首字母决定;所以如果我们想跳过 lazy 触发 eager 编译,我们应该在前面加位操作符,例如'!|~'。我们直接看代码:

let a = 0; //顶层的代码都是 eager
// 立即执行函数表达式 IIFE = Immediately Invoked Function Expression
(function eager() {...})(); // 函数体是 lazy
// 顶层的函数非IIFE
function lazy() {...} // 函数体是 lazy
// 后续执行时
...
lazy(); // ->eager 开始解析和编译!
// 启示,通过这种方式触发eager解析
!function eager2() {...}, function eager3() {...} // All eager
 
// 错误的case!
let f2 = function lazy() {...}(); // 先触发了lazy 解析, 然后又eager解析

Lazy 和 Eager 为什么都很重要?

  • 我们需要lazy解析器, 因为web页面会使用很多无关代码;(事实,摆手)
  • 如何选择呢?

    • 如果我们eager解析了我们无关代码,我们在浪费时间;
    • 如果我们lazy解析了我们有关代码,我们将多支付预解析的时间:0.5 x 解析时间 + 1 x 解析时间 = 1.5 解析时间
  • 假设我们只知道我们的启动代码,并不知道具体会执行哪些代码。(事实again,摆手)

强迫执行 eager 解析

  • Optimize.js 用括号括住它认为将被执行的函数。
浏览器 使用 optimize-js 后通常启动速度提升
Chrome 55 20.63%
Edge 14 13.52%
Firefox 50 8.26%
Safari 10 -1.04%
  • 实际上我们只需要

    1. 解析编译正确的函数;
    2. 最小化我们失败的代价;
  • 在此基础上迭代

Web 开发者如何利用 V8 的解析器?

使用更少的代码!

  • JavaScript 的启动性能;
  • 使用更少的 JavaSCript: 使用 Chrome Dev Tools 的 code coverage 功能;
  • 衡量你的代码解析开销:chrome://tracingv8.runtime_stats

代码缓存 + Bundling

  • 代码缓存: V8 会缓存经常使用的 JavaScript 的字节码;
  • Bundling: 如果你更新了 bundle 的一部分代码,将失去整个 bundle 缓存;
  • 避免使用 eval

Web 开发者: 使用 streaming

  • 流式 JavaScript: 并行下载和解析;
  • 体积大的 JavaScripts

    • 尽可能早的异步读取;
    • 确保流式 JavaScript 运转 chrome://tracing

括号黑魔法

  • 使用括号技巧选中需要 eager 解析并编译的关键路径:

    • 旧版本的 Chrome;
    • 跨浏览器;
    • 现在就要提升性能,立刻马上!(等不及我们去修复)

额外内容

  • V8 解析器是一款 V8 递归下降编译器;
  • 大约~15k 行C++ 还有 ~7k 行C, for the AST+Scopes
目录
相关文章
|
JavaScript 前端开发
学习JavaScript笔记
学习JavaScript笔记
43 0
|
前端开发 JavaScript
Javascript 学习 笔记六
1、javascript 面向对象 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xht
1025 0
|
XML JavaScript 前端开发
Javascript 学习 笔记五
1、事件冒泡 在一个对象上触发某类事件(比如单击onclick事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,直至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层次的最顶层,即document对象(有些浏览器是window)。 2、offset、scro
1214 0
|
9天前
|
Kubernetes 安全 Devops
【云效流水线 Flow 测评】驾驭云海:五大场景下的云效Flow实战部署评测
云效是一款企业级持续集成和持续交付工具,提供免费、高可用的服务,集成阿里云多种服务,支持蓝绿、分批、金丝雀等发布策略。其亮点包括快速定位问题、节省维护成本、丰富的企业级特性及与团队协作的契合。基础版和高级版分别针对小型企业和大规模团队,提供不同功能和服务。此外,云效对比Jenkins在集成阿里云服务和易用性上有优势。通过实战演示了云效在ECS和K8s上的快速部署流程,以及代码质量检测和AI智能排查功能,展示了其在DevOps流程中的高效和便捷,适合不同规模的企业使用。本文撰写用时5小时,请各位看官帮忙多多支持,如有建议也请一并给出,您的建议能帮助我下一篇更加出色。
136083 13
|
14天前
|
存储 Prometheus 并行计算
10倍性能提升-SLS Prometheus 时序存储技术演进
本文将介绍近期SLS Prometheus存储引擎的技术更新,在兼容 PromQL 的基础上实现 10 倍以上的性能提升。同时技术升级带来的成本红利也将回馈给使用SLS 时序引擎的上万内外部客户。
158386 5
|
17天前
|
设计模式 前端开发 JavaScript
卓越工程布道:掌握条件判断的模式
本文是普适性的经验分享,并非按规范局限在 JavaScript 前端视角 做出的总结,除JavaScript外还深入结合了ActionScript 3.0、PHP、C / C++、Basic非纯粹OOP领域语言的经验。
241698 54
|
10天前
|
存储 SQL Apache
阿里云数据库内核 Apache Doris 基于 Workload Group 的负载隔离能力解读
阿里云数据库内核 Apache Doris 基于 Workload Group 的负载隔离能力解读
阿里云数据库内核 Apache Doris 基于 Workload Group 的负载隔离能力解读
|
15天前
|
人工智能 弹性计算 算法
一文解读:阿里云AI基础设施的演进与挑战
对于如何更好地释放云上性能助力AIGC应用创新?“阿里云弹性计算为云上客户提供了ECS GPU DeepGPU增强工具包,帮助用户在云上高效地构建AI训练和AI推理基础设施,从而提高算力利用效率。”李鹏介绍到。目前,阿里云ECS DeepGPU已经帮助众多客户实现性能的大幅提升。其中,LLM微调训练场景下性能最高可提升80%,Stable Difussion推理场景下性能最高可提升60%。
|
11天前
|
存储 弹性计算 Cloud Native
1 名工程师轻松管理 20 个工作流,创业企业用 Serverless 让数据处理流程提效
为应对挑战,语势科技采用云工作流CloudFlow和函数计算FC,实现数据处理流程的高效管理与弹性伸缩,提升整体研发效能。
64451 1
|
17天前
|
消息中间件 安全 API
Apache RocketMQ ACL 2.0 全新升级
RocketMQ ACL 2.0 不管是在模型设计、可扩展性方面,还是安全性和性能方面都进行了全新的升级。旨在能够为用户提供精细化的访问控制,同时,简化权限的配置流程。欢迎大家尝试体验新版本,并应用在生产环境中。
187110 6