WebAssembly与即时编译器JIT
背景介绍
简介
WebAssembly或wasm是一个实验性的低端编程语言,应用于浏览器内的客户端。WebAssembly是便携式的抽象语法树,被设计来提供比JavaScript更快速的编译及运行。WebAssembly将让开发者能运用自己熟悉的编程语言(最初以C/C++作为实现目标)编译,再藉虚拟机引擎在浏览器内运行。WebAssembly的开发团队分别来自Mozilla、Google、Microsoft、Apple,代表着四大网络浏览器Firefox、Chrome、Microsoft Edge、Safari。2017年11月,以上四个浏览器都开始实验性的支持WebAssembly。WebAssembly 于 2019 年 12 月 5 日成为万维网联盟(W3C)的推荐,与 HTML,CSS 和 JavaScript 一起,成为 Web 的第四种语言。(from wiki)
什么是WebAssembly
WebAssembly就是一段二进制代码,其本质是一个编译目标。
产生的契机
JavaScript 诞生于 1995 年。JavaScript是一种解释型语言、弱类型,它并不是为了快速运行而设计出来的,而且在最初十年里它的运行速度确实不够快。
优化阶段一: JIT
在 2008 年,这个被大家称为 性能战争 的时期开始了。很多浏览器添加了 即时编译器(just-in-time compilers),也被称作 JIT。当 JavaScript 代码运行时,JIT 可以识别代码运行的模式,并基于这些模式来加速代码的运行。
基本思想是,在 js 引擎中增加监视器,将可能会多次执行的代码段通过编译器编译存储,在下次调用时直接调用,并且对代码段进行优化。
由于 JIT 的出现,JavaScript 的性能迎来了一个拐点,JavaScript 的执行速度快了之前 10 倍。
随着性能的提高,JavaScript 开始被用于一些 令人难以预料 的领域,比如使用 Node.js 进行服务端编程。性能的提高使得 JavaScript 可以解决一类全新的问题。
JIT 的问题:
使执行的速度变快的同时,也带来了新的(主要是内存)性能开销
JIT的优化机制会因为类型不确定而产生性能问题
优化阶段二:各种方案
针对于类型不确定产生的性能问题,产生了多种方案:
微软的 Typescript 通过为 JS 加入静态类型检查来改进 JS 松散的语法,提升代码健壮性;
火狐的 asm.js 是取 JS 的子集,JS 引擎针对 asm.js 做性能优化;
谷歌的 Dart/PNaCl 是为浏览器引入新的虚拟机去直接运行 Dart/C/C++ 程序以提升性能;
但是,每个方案其实都有各自的问题:
Typescript 最后还是编译成 JS,性能没有太大的提升;
asm.js 开发效率低;
Dart/PNaCl 只能在 Chrome 里运行;
最终,大佬们达成一致,觉得 asm.js 这个方式有前途,于是标准化了一下,就是 WebAssembly。
WebAssembly 可能会带来另外另一个拐点。
工作原理
浏览器中的JavaScript如何运行
当你作为一个开发者,将 JavaScript 添加到页面的时候,你就有了一个目标 和 一个难题。
目标:告诉计算机应该做什么。
难题:你和计算机说的是不同的语言。
JavaScript 或 其他高级编程语言 其实都是人类语言。它们是为人类认知而设计,而不是机器。
JS引擎的工作就是将你的人类语言 翻译 为机器能够理解的机器语言。
在编程中,通常有两种 翻译 为机器语言的方式。我们可以使用 解释器(interpreter) 或者 编译器(compiler)。
在使用 解释器 时,这种 翻译 几乎是 一行一行 实时进行的。
编译器 使用另一种方式,它会提前 翻译 好 并且记录下来,而不是动态进行 翻译 。
解释器 的 优缺点
解释器 可以迅速开始工作 并 运行代码。不必完成整个编译阶段,就能开始运行代码了。可以一行一行地 翻译 并运行。
解释器似乎很自然地适用于 JavaScript。因为对于 Web 开发者来说,能够快速运行自己的代码是很重要的。这确实是浏览器最初使用 JavaScript 解释器的原因。
但是,当不止一次地运行相同代码时,解释器的缺点就出现了 。例如,在一个循环中,它将不得不 一次又一次的做出同样的 翻译 。
编译器 的 优缺点
编译器 权衡利弊的方式 和 解释器是相反的。
因为必须先进行编译阶段,所以它启动需要花更多的时间。但由于不需要在循环中重复进行 翻译 ,在循环中的代码会运行的更快。
另外一个不同之处是,编译器有更多的时间对代码进行分析和修改,以便代码能够运行的更快。这种修改被称为 优化(optimization)。
解释器 作用在 运行时(runtime),所以在 翻译 时没有足够的时间来计算这些 优化 。
即时编译器JIT(Just-in-time compiler)
没有 JIT 的浏览器执行 JavaScript 的过程
为了避免解释器的低效性,即每次经过循环都需要重新 翻译 代码,浏览器开始混合加入编译器。
不同的浏览器以略微不同的方式实现这一点,但基本思想是一致的。它们将 监视器 作为一个新部件 添加到浏览器引擎中。监视器 在代码开始运行时开启监控,并记录代码 运行的次数 和 使用到的类型。
一般来说,JavaSciript 引擎都有一个包含解释器和优化编译器的处理流程。其中,解释器可以快速生成未优化的字节码,而优化编译器会耗费更长的时间,但最终可生成高度优化的机器码。
????????
先了解几个概念:
Ignition | V8引擎中的解释器(interpreter) |
---|---|
Turbofan | V8引擎中的优化编译器(optimizing compiler) |
Baseline | SpiderMonkey引擎中的基线编译器 |
IonMonkey | SpiderMonkey引擎中的优化编译器 |
桩代码(Stub) | 用来替换一部分功能的程序段 |
warm | 用来标记代码执行频次,如果同一行代码运行过几次,那么这段代码就被称为 warm |
hot | 用来标记代码执行频次,如果同一行代码运行了非常多次,那么这段代码就被称为 hot |
优化 | 为了使代码运行更快,优化编译器做出一些假设,生成优化代码 |
去优化 | 代码回退到解释器或者基线编译器编译的版本进行执行 |
V8(Chrome和Node使用的JS引擎)
SpiderMonkey(Firefox使用的JS引擎)
我们以SpiderMonkey为例详细说明一下:
最初 ,监视器 只通过 解释器 运行所有代码。
基线 编译器(Baseline compiler)
当一个函数开始变 warm,JIT 就将它发送出去进行编译。之后 JIT 会将编译结果存储下来。
函数的每一行代码都会分别被编译成一段 “桩代码”,桩代码 根据 行号 和 变量类型 进行索引。如果 监视器 发现代码执行中出现了 执行过的相同代码(且使用的变量类型相同),那么就会取出 已编译 的版本。
这有助于加速代码运行。但是就像我在上文提到的,编译器能做到的不仅仅是这些。它还能花些时间来计算出代码最有效率的执行方式,然后进行优化。
基线编译器 会执行上面提到的优化。但优化花费时间不应该太长,这会导致代码执行被阻塞。
可是,如果代码真的非常 hot ,即代码运行了非常多次,那么花费额外的时间去做些优化就是值得的。
优化编译器(Optimizing compiler)
当一部分代码非常 hot 的时候,监视器 就会将它发送到 优化编译器。这将创建并保存另一个版本的函数代码,这份代码会比原版的运行更快。
为了使代码运行更快,优化编译器不得不做出一些假设。
例如,如果可以假设 所有的对象都由一个 特定形式构造函数 创建 — 即对象总是具有相同且添加顺序相同的属性名,那么就可以基于这个假设,走一些捷径。
通过监视器观测代码执行收集到的信息,优化编译器就可以做出判断。如果某件事在之前的循环都是发生的,那么假定这件事还会继续发生。
但是,当然,对于 JavaScript,这是得不到保证的。就算有 99 个对象都有相同的结构,第 100 个也有可能缺失一个属性。
所以,需要 在编译后的代码运行之前,对其进行检查,确认之前的假设是否还有效。如果通过检查,那么就执行代码。但如果没有通过,JIT 会认为 自己之前做出了错误的假设,并清除优化版本的代码(去优化)。
通常,优化编译器会加速代码运行,但是有时也会导致意外的性能问题。如果代码持续不断的进行 优化 ,然后 去优化,那么最终 这会比直接执行 基线编译器编译的代码 还要慢。
大多数浏览器会增加一些限制,用来打断可能出现的 优化/去优化 循环周期。如果 JIT 已经做了 比方说 10 次优化,并且不断的将优化代码清除,那么它就会放弃再进行优化了。
这就是 JIT。它 监控代码运行情况 并 将 hot 代码进行优化,以此让 JavaScript 运行得更快。是它的出现让大多数 JavaScript 应用程序的性能提高了许多倍。
尽管有了这些改进,JavaScript 的性能还是不稳定的。为了让性能整体更快,JIT 运行时会有许多开销,包括:
优化 和 去优化
监视器 记录监视信息 所消耗的内存
去优化 的恢复信息 消耗的内存
存储 基线 编译器 和 优化编译器 不同的函数编译版本 所消耗的内存
WebAssembly
WebAssembly 所处的位置
早期的JS:
当前的JS:
WebAssembly 与典型 web 应用对比
展望
2019年12月份,正式加入w3c规范,作为和html,css,js并行的第四种语言
目前看来 WebAssembly 主要的两个点是:
适合用于大量密集计算,且无需频繁与 JS 和 DOM 交互的场景,比如音视频处理,dom diff
可以实现代码库的复用
作者:过境千帆
链接:https://juejin.cn/post/7054465321230401566