简述WebAssembly

751 阅读8分钟

简述WebAssembly

webAssembly是什么

WebAssembly是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如C / C ++等语言提供一个编译目标,以便它们可以在Web上运行。它也被设计为可以与JavaScript共存,允许两者一起工作。

WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式

JavaScript在浏览器中的运行方式

为了让机器可以读懂代码,一般都需要一个“翻译器”来翻译我们的代码

常见的两种翻译器:解释器和编译器

解释器:一边翻译代码一边执行代码

解释器的优点:启动和执行的更快,不需要等待全部编译完成就可以运行代码,正是因为这个原因,解释器更适合JavaScript。我们总是希望页面可以快速加载减少等待时间;

解释器的缺点:同样的代码当你需要运行多次的时候,解释器不得不重新再编译一遍同样的代码,这样的话效率就变得低很多了

翻译器:将所有代码先进行编译,生成目标文件再到机器上运行。对于同样的代码不需要重复执行

JavaScript代码的执行过程

image.png 总结一下

  • 获取JS源代码,使用Parser(解析器)将代码转换为AST(抽象语法树)
  • ByteCode Compiler(Ignition)会将AST编译成引擎可以识别的字节码
  • 字节码进入翻译器,将字节码翻译成执行效率很高的Machine Code(机器码)

Just-in-time编译器

结合了解释器和编译器的优点,形成混合模式

原理

不同浏览器实现Just-in-time的方式不同,但思想是一样的。在JavaScript引擎中添加一个“监控器”,来监控代码的运行情况,记录代码一共运行多少次、如何运行的信息; 监控器会监视着所有通过解释器的代码,如何一段代码被重复运行了几次,监视器就会给这个代码段添加一个“warm”的标签,如果一段代码被重复运行了好多次,监控器就会给这个代码段添加一个“hot”的标签;

基线编译器

当一段代码被标记为“warm”或者“hot”,JIT就会将这段代码送去基线编译器,并将结果存储起来;代码段段每一行都会被编译成一个“stub(桩)”,同时会给这个桩分配一个“行号+变量类型”的索引;当监控器检查到了执行同样的代码和同样的变量类型,那就直接将这个已经编译好的代码直接push出来给浏览器;

优化编译器(优化和去优化)

如果一段代码变得“very hot”之后,监控器就会把他发送到优化编译器中去,生成一个更加高效更加快速的代码版本出来并且储存下来;

为了生成一个更快速的代码版本,优化编译器必须做一些假设。假如由同一个构造函数生成的实例都具有相同的形状---就是说所有的实例都具有相同的属性,并且有同样的代码执行顺序,那么优化编译器就可以对此进行优化; 如果循环中每次迭代的对象都是相同的形状,那么可以认为他之后的迭代对象都是相同的形状(优化)

然而,JavaScript是一种弱类型的语言,存在一种可能:前面n-1个对象是同样的形状,但是第n个就不同了。正是由于这种情况,需要在运行代码之前检查优化编译器的假设是不是合理的,假如是不合理的,就认为JIT做了一个错误的假设,将优化后的代码丢掉,重新进入解释器进行执行(去优化)。如果是合理的,那么优化后的代码就直接执行。

总结

JIT是什么呢?他是一种通过监控代码的运行状态,把“hot”代码进行优化,减少翻译和解析代码的时间。通过这种方式实现JavaScript的性能优化

JavaScript的性能发展

JavaScript于1995年问世,

浏览器引入Just-in-time编译器,使得JavaScript性能达到了一个转折点,js代码执行速度快了10倍

image.png 通过使用WebAssembly,浏览器可能处于第三个转点

image.png

WebAssembly工作原理

WebAssembly性能

JS引擎在图中各部分花费的时间取决于JavaScript代码,图中比例不代表真实情况下代码执行时间的比例 JavaScript代码在浏览器中各部分花费时间一览图 image.png

  • Parse ---将源代码解析成AST花费的时间
  • Compile + Optimize ----基线编译器和优化编译器编译代码花费的时间
  • Re-optimize ----去优化操作花费的时间
  • Execute ---执行代码花费的时间
  • Garbage collection ---垃圾回收、清理内存花费的时间

JavaScript与WebAssembly性能对比图

image.png

文件获取

这一步虽然没有体现在上图👆中,但是从服务器获取文件的步骤依然要花费一部分时间, WebAssembly是二进制代码压缩体积要比JavaScript更小,所以说WebAssembly在客户端和服务器直接传输效率更高

解析

JavaScript源码需要Parser将源代码解析为AST,然后Ignition(Ignition也可以直接执行字节码文件)将AST解析为字节码文件提供给js引擎进行执行;

然而,WebAssembly不需要这种解析,因为它本身就是中间代码,只要解码和检查代码没有问题就可以了

image.png

编译和优化

在JIT的介绍中,我们知道了编译是在代码执行的过程中进行的。因为JavaScript是弱类型语言,不同的参数类型JIT会编译成为不同的版本;

WebAssembly会编译的更快

  • 在编译优化之前,不需要运行就知道所有参数的类型,所有的参数类型都是确认的,不会变化;
  • 同样的代码不会编译成多个版本;
  • 很多优化在LLVM就已经做好了,没有太多优化内容;

image.png

去优化

JavaScript代码优化可能会发生“优化去优化优化去优化”这个过程 当JIT假设的优化在执行阶段发现假设的代码有问题会丢掉优化的代码,将机器码反编译回字节码返回解释器,但是如果这段代码依然重复调用,优化编译器会再次对这个代码进行优化。反复横跳,其实是在做无用功

但是在WebAssembly中,所有的类型都是确定的,不需要JIT进行假设优化

image.png

执行

我们写的JavaScript代码需要JIT编译之后执行,但是开发者为了代码的可读性和健壮性通常不会考虑代码是否符合JIT的优化机制,所以都会有依赖于运行环境JIT对代码的优化度;
WebAssembly基本上不需要JIT进行优化,而且它本身的设计也不是给开发者读懂的,所以她天然的贴近机器语言,拥有更高的执行效率

image.png

内存回收

JavaScript中不需要关心垃圾回收的问题,js引擎会自动做这个事情。垃圾回收会自动开始,并且不受开发者控制,浏览器会分配给一个合理的开始时间来进行垃圾回收,但是还是会增加代码执行的开销;

目前为止,WebAssembly不支持垃圾回收,所有的内存都是手动分配和管理的;

image.png

高级语言转换为汇编语言

编译器的前端把高级语言翻译到 IR,编译器的后端把 IR 翻译成目标机器的汇编代码(intermediate representation,IR)

image.png

每一种目标汇编语言(ARM、X86)都是依赖于特定的机器结构的、当你把你的代码放到用户的机器上运行的时候,你并不知道用户的机器结构是什么

ARM架构主要用户移动设备,比如iOS和Android,而Intel架构主要用于台式电脑

然而WebAssembly跟其它的汇编语言不一样,它并不依赖具体的物理机器。可以抽象的理解它是概念机器的机器语言,而不是实际的物理机器的机器语言
正因为如此,它也被称为虚拟指令,它比JavaScript更直接的映射到机器码。代表了“如何能在通用的硬件上更有效的执行代码”的理念,所以它也不可能直接映射为机器码;

image.png image.png

WebAssembly与JavaScript的关系

参考

hacks.mozilla.org/2017/02/a-c… www.ibm.com/docs/en/ztp… juejin.cn/post/705661… mbebenita.github.io/WasmExplore… webassembly.org.cn/