JavaScript是如何工作的:与 WebAssembly比较 及其使用场景

前端小智

这是专门探索 JavaScript 及其所构建的组件的系列文章的第6篇。

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

如果你错过了前面的章节,可以在这里找到它们:

  1. JavaScript是如何工作的:引擎,运行时和调用堆栈的概述!
  2. JavaScript是如何工作的:深入V8引擎&编写优化代码的5个技巧
  3. JavaScript如何工作:内存管理+如何处理4个常见的内存泄漏
  4. JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!
  5. JavaScript是如何工作: 深入探索 websocket 和HTTP/2与SSE +如何选择正确的路径!

这次将讲解 WebAssembly 是如何工作的,更重要的是,它是如何在性能方面与JavaScript进行比较的:加载时间、执行速度、垃圾收集、内存使用、API开放平台、调试、多线程和可移植性。

首先,让我们看看WebAssembly做什么

首先,我们有必要了解一下asm.js。2012年,Mozilla 的工程师 Alon Zakai 在研究 LLVM 编译器时突发奇想:许多 3D 游戏都是用 C / C++ 语言写的,如果能将 C / C++ 语言编译成 JavaScript 代码,它们不就能在浏览器里运行了吗?众所周知,JavaScript 的基本语法与 C 语言高度相似。于是,他开始研究怎么才能实现这个目标,为此专门做了一个编译器项目 Emscripten。这个编译器可以将 C / C++ 代码编译成 JS 代码,但不是普通的 JS,而是一种叫做 asm.js 的 JavaScript 变体,性能差不多是原生代码的50%。

之后Google开发了Portable Native Client,也是一种能让浏览器运行C/C++代码的技术。 后来可能是因为彼此之间有共同的更高追求,Google, Microsoft, Mozilla, Apple等几家大公司一起合作开发了一个面向Web的通用二进制和文本格式的项目,那就是WebAssembly。asm.js 与 WebAssembly 功能基本一致,就是转出来的代码不一样:asm.js 是文本,WebAssembly 是二进制字节码,因此运行速度更快、体积更小。

WebAssembly(又称 wasm) 是一种新的字节码格式,主流浏览器都已经支持 WebAssembly。 和 JS 需要解释执行不同的是,WebAssembly 字节码和底层机器码很相似可快速装载运行,因此性能相对于 JS 解释执行大大提升。 也就是说 WebAssembly 并不是一门编程语言,而是一份字节码标准,需要用高级编程语言编译出字节码放到 WebAssembly 虚拟机中才能运行, 浏览器厂商需要做的就是根据 WebAssembly 规范实现虚拟机。

WebAssembly 加载时间

WebAssembly 在浏览器中加载速度更快,因为只有已经编译好的 wasm 文件需要通过internet传输。wasm 是一种低级汇编语言,具有非常简洁的二进制格式。

WebAssembly 执行速度

如今 Wasm 运行速度只比原生代码慢 20%,这是一个令人惊喜的结果。它是这样的一种格式,会被编译进沙箱环境中且在大量的约束条件下运行以保证没有任何安全漏洞或者使之强化。和真正的原生代码比较,执行速度的下降微乎其微。更重要的是,未来将会更加快速。

更好的是,它与浏览器无关——所有主要引擎都增加了对 WebAssembly的支持,且执行速度相差无几。

为了理解与JavaScript相比WebAssembly的执行速度有多快,应该首先阅读关于JavaScript引擎如何工作的文章。

让我们快速浏览下 V8 的运行机制:

图片描述

在左边,是一些JavaScript源代码,包含JavaScript函数。首先需要解析它,以便将所有字符串转换为标记并生成抽象语法树(AST)。AST 是JavaScript程序逻辑结构在内存中的表示形式。一旦生成了 AST,V8 直接进入到机器码阶段。其后遍历树,生成机器码,就得到了编译好的函数,在这个过程中是没有提高遍历速度的。

现在,让我们看看V8管道在下一阶段的工作:

图片描述

现在有了V8 的新的优化编译器 (TurboFan), 当 JavaScript应用程序在运行时,很多代码都在 V8 中运行。TurboFan 监测是否有代码运行缓慢,是否存在性能瓶颈和热点(内存使用过高的地方),以便对其进行优化。它把以上监视得到的代码推向后端即优化过的即时编译器,该编译器把消耗大量 CPU 资源的函数转换为性能更优的代码。

它解决了性能的问题,但这种处理方式有个缺点,分析代码和决定优化哪些内容的过程也会消耗CPU,这意味着更高的耗电量,特别是在移动设备上。