先来几个名词
解释器
一行行地边解释边执行
编译器
是把源代码整个编译成目标代码,执行时不再需要编译器,直接在支持目标代码的平台上运行。
解释器的利弊
解释器启动和执行的更快。你不需要等待整个编译过程完成就可以运行你的代码。从第一行开始翻译,就可以依次继续执行了。
正是因为这个原因,解释器看起来更加适合 JavaScript。对于一个 Web 开发人员来讲,能够快速执行代码并看到结果是非常重要的。
这就是为什么最开始的浏览器都是用 JavaScript 解释器的原因。可是当你运行同样的代码一次以上的时候,解释器的弊处就显现出来了。比如你执行一个循环,那解释器就不得不一次又一次的进行翻译,这是一种效率低下的表现。
编译器的利弊
- 编译器的问题则恰好相反。
它需要花一些时间对整个源代码进行编译,然后生成目标文件才能在机器上执行。对于有循环的代码执行的很快,因为它不需要重复的去翻译每一次循环。
Just-in-time 及时编译(简称JIT)
为了解决解释器的低效问题,后来的浏览器把编译器也引入进来,形成混合模式。
不同的浏览器实现这一功能的方式不同,不过其基本思想是一致的。在 JavaScript 引擎中增加一个监视器(也叫分析器)。监视器监控着代码的运行情况,记录代码一共运行了多少次、如何运行的等信息。
起初,监视器监视着所有通过解释器的代码。
如果同一行代码运行了几次,这个代码段就被标记成了 “warm”,如果运行了很多次,则被标记成 “hot”。
基线编译器
如果一段代码变成了 “warm”,那么 JIT 就把它送到编译器去编译,并且把编译结果存储起来。如果监视器监视到了执行同样的代码和同样的变量类型,那么就直接把这个已编译的版本 push 出来给浏览器。
优化编译器
如果一个代码段变得 “very hot”,监视器会把它发送到优化编译器中。生成一个更快速和高效的代码版本出来,并且存储之。
更多工作原理介绍请移步 JIT工作原理
JavaScript性能变化曲线
JIT不局限于JS?
JIT总结
简而言之 JIT 是什么呢?它是使 JavaScript 运行更快的一种手段(JIT,内联缓存和隐藏类)之一,通过监视代码的运行状态,把 hot 代码(重复执行多次的代码)进行优化。通过这种方式,可以使 JavaScript 应用的性能提升很多倍。
为了使执行速度变快,JIT 会增加很多多余的开销,这些开销包括:
- 优化和去优化开销
- 监视器记录信息对内存的开销
- 发生去优化情况时恢复信息的记录对内存的开销
各大厂商的方案
- 微软的 TypeScript 通过为 JS 加入静态类型检查来改进 JS 松散的语法,提升代码健壮性;
- 谷歌的 Dart 则是为浏览器引入新的虚拟机去直接运行 Dart 程序以提升性能;
- 火狐的 asm.js 则是取 JS 的子集,JS 引擎针对 asm.js 做性能优化。
以上尝试各有优缺点
- TypeScript 只是解决了 JS 语法松散的问题,最后还是需要编译成 JS 去运行,对性能没有提升;
- Dart 只能在 Chrome 预览版中运行,无主流浏览器支持,用 Dart 开发的人不多;
- asm.js JS语法子集、有很大限制,开发效率低。关于Asm.js的一些基本介绍和它的实现
三大浏览器巨头分别提出了自己的解决方案,互不兼容,这违背了 Web 的宗旨; 是技术的规范统一让 Web走到了今天,因此形成一套新的规范去解决 JS 所面临的问题迫在眉睫。
于是 WebAssembly 诞生了
WebAssembly(Google , Microsoft , Mozilla , Apple 等几家大公司合作发起的)
WebAssembly 是一种新的字节码格式,主流浏览器都已经支持 WebAssembly。 和 JS 需要解释执行不同的是,WebAssembly 字节码和底层机器码很相似可快速装载运行,因此性能相对于 JS 解释执行大大提升。 也就是说 WebAssembly 并不是一门编程语言,而是一份字节码标准,需要用高级编程语言编译出字节码放到 WebAssembly 虚拟机中才能运行.
WebAssembly优点
- 体积小:由于浏览器运行时只加载编译成的字节码,一样的逻辑比用字符串描述的 JS 文件体积要小很多;
- 加载快:由于文件体积小,再加上无需解释执行,WebAssembly 能更快的加载并实例化,减少运行前的等待时间;
能编译成 WebAssembly 字节码的高级语言有:
- AssemblyScript:语法和 TypeScript 一致,对前端来说学习成本低。
- c\c++:官方推荐的方式
- Rust:语法复杂、学习成本高,对前端来说可能会不适应。
- Kotlin:语法和 Java、JS 相似,语言学习成本低。
WebAssembly是如何工作的
使用高级语言来编写WebAssembly模块,将其编译成.wasm文件。这些.wasm文件并不能直接被浏览器识别,所以它们需要一种称为JavaScript胶接代码(glue code,用于连接相互不兼容的组件,详见:http://whatis.techtarget.com/definition/glue-code)的东西来加载。
1.Emscripten编译器
C到WebAssembly的编译器,推荐使用Emscripten(https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html),安装这个工具费时费力费空间。
有一个在线 C/C++ 转 wasm 的工具: WasmExplorer
JavaScript胶接代
function loadWebAssembly (path) {
return fetch(path) // 加载文件
.then(res => res.arrayBuffer()) // 转成 ArrayBuffer
.then(WebAssembly.instantiate) // 编译 + 实例化
.then(mod => mod.instance) // 提取生成都模块
}
其实就是 【加载文件】->【转成 buffer】->【编译】->【实例化】
DEMO C语言到WebAssembly
math.c
int square (int x) {
return x * x;
}
loader.js
见上节JavaScript胶接代
兼容性
各大主流浏览器都已经支持!
案例
- 在2017年5月时,白鹭引擎宣布开始支持 WebAssembly,而利用 WebAssembly,白鹭引擎可以将 HTML 5 代码编译为机器码运行,让游戏运行性能提升 300%。
- 2017年10月底,谷歌开始支持让 Google Earth 在 Firefox 上运行,其中的关键就是使用了 WebAssembly。
- TeaVM 是一个 AOT 编译器(翻译器),可将 Java 字节码翻译成 JavaScript 或 WebAssembly 格式。
- Blazor — 让 .NET 代码也能在浏览器运行
开源 UI 框架 Blazor 可以让 .NET 代码在浏览器环境中运行,而习惯 ASP.NET Razor 语法的开发者,仍可以继续沿用习惯的开发模式
WebAssembly也许是JS性能的第二个拐点,未来可期
既然编译好后的机器码执行速度很快,为啥浏览器不尝试直接支持编译型语言??
- JavaScript的历史地位,WebAssembly与其相辅相成.JavaScript巨大的生态系统和友好的语法,WebAssembly接近原生的表现性能。
- WebAssembly 字节码非常接近机器码,可以非常快的被翻译为对应架构的机器码,因此 WebAssembly 运行速度和机器码接近。
- 类比Java 字节码