# WebAssembly

# 历程

# 何为WebAssembly

# 概述

一种低层次的、安全的、可移植的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如C / C ++等语言提供一个编译目标,以便它们可以在Web上运行.它也被设计为可以与JavaScript共存,允许两者一起工作(通过WebAssembly JS API)。

# 直观模样

DETAILS

左侧是用C++实现的求递归的函数。右侧是指令文本。而中间的十六进制Binary Code就是webassembly。

WebAssembly的可读性非常差,这是因为WebAssembly是一个编译目标(编译目标就比如:当我们写TypeScript的时候,Webpack最后打包生成的JavaScript文件就是编译目标),是从高级语言转换到机器指令文本间的胶接代码(glue code)。上图的Binary就是左侧的C++代码经过编译器编译之后的结果。

# 既生JS何生WebAssembly

# 明确关系

WebAssembly并没有要替代JavaScript的意思,而是互为补充的。总结下来就两个点:

  • 给了Web更好的性能。WebAssembly是asm.js的性能的2~6倍;
  • 给了Web更多的可能。随着WebAssembly的技术越来越成熟,势必会有更多的应用,从Desktop被搬到Web上,这会使本来已经十分强大的Web更加丰富和强大。

# WebAssembly诞生背景

# JavaScript的性能瓶颈:

JavaScript于95年问世,在前 10 个年头执行速度确实不快。在08年,浏览器市场竞争激烈,打响“性能大战”,浏览器厂商纷纷引入了 Just-in-time 编译器,也叫 JIT模式,JS的执行速度快了10倍,JS也终于应用到了新的领域,比如后端开发nodejs。

  • JIT编译(just-in-time compilation) 狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。JIT编译一词后来被 泛化,时常与动态编译等价。
  • 动态编译(dynamic compilation) 指的是“在运行时进行编译”;与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation)。

JS没有静态变量类型,大大降低了效率:

  • js代码在引擎中的执行过程: 需要首先被下载,然后进入parser转成AST(抽象语法树),然后根据AST,Bytecode Compiler(字节码编译器)会生成引擎可识别的bytecode(字节码),最后字节码进入翻译器翻译成 Machine Code(机器码)。
  • 效率为啥低: 引擎会对执行次数较多的function进行优化,将其编译成Machine Code后打包送到顶部的 JIT Compiler,下次再执行这个function,就会直接执行编译好的Machine Code。但是由于JS的动态变量,类型变化后上一次所做的优化就失效了只能丢弃,再一次进行优化,因此效率低。

# Asm.js出现

为了解决js低性能问题,asm.js诞生了,它是WebAssembly的前身,是JavaScript的严格子集。Asm.js也不是给开发者手写的,也是一个编译目标。

  • Asm.js 为了解决JS的两个低性能问题而设计的:它的变量一律都是静态类型,并且取消垃圾回收机制

    • Asm.js只提供两种数据类型:整数和浮点数,其他类型均不提供,其他类型均以数值的形式存在。Asm.js 要求事先声明类型且不得改变,这样就节省了类型判断的时间。asm.js的类型声明有固定写法,变量 | 0表示整数,+变量表示浮点数。
    • asm.js 没有垃圾回收机制,所有内存操作都由程序员自己控制。
    var a = 1;
    
    var x = a | 0;  // x 是32位整数
    var y = +a;  // y 是64位浮点数
    
  • Asm.js不能解决所有的问题:

    • asm.js 始终逃不过要经过Parser,要经过ByteCode Compiler,而这两步是JavaScript代码在引擎执行过程当中消耗时间最多的两步。而WebAssembly不用经过这两步,直接就是bytecode(字节码)。这就是WebAssembly比asm.js更快的原因。
    • asm.js只有Mozzila支持,而WebAssembly是Mozzila、Google、Microsoft、Apple以及一些其他组织拟联手制定的游戏规则,是业界大佬的统一规范。

# WebAssembly应用而生

在上述背景之下,15年,我们迎来了WebAssembly。WebAssembly是经过编译器编译之后的代码,体积小、起步快。在语法上完全脱离JavaScript,同时具有沙盒化的执行环境,成为一个相对独立的编译器目标语言,这样可以不必为了本地代码的运行,而在JS中引入太多内容,将来Wasm和JS会是分工合作的关系。

# WebAssembly实操

必须要安装 编译器Emscripten,建议阅读开发者引导 (opens new window),同时还需要使用WebAssembly JS API(API参考 (opens new window))。

  1. WebAssembly在Node中的应用:
const fs = require('fs');
let src = new Uint8Array(fs.readFileSync('./test.wasm'));
const env = {
	memoryBase: 0,
	tableBase: 0,
	memory: new WebAssembly.Memory({
		initial: 256
	}),
	table: new WebAssembly.Table({
		initial: 2,
		element: 'anyfunc'
	}),
	abort: () => {throw 'abort';}
}
WebAssembly.instantiate(src, {env: env})
.then(result => {
	console.log(result.instance.exports._add(20, 89));
})
.catch(e => console.log(e));
  1. WebAssembly在React当中的应用:
  • add.c(简单加法和斐波那契)
#include <stdio.h>

int add(int a, int b) {
   return a + b;
}

int fibonacci(int n) {
    if (n <= 1) {
        return n;
    } else {
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}
  • App.js
import React, {Component} from 'react';
import fetch from 'node-fetch';
import './App.css';
import wasmC from './add.c';

class App extends Component {
  constructor() {
    super();
    this.state = {
      jsFibonacci: null,
      cFibonacci: null
    }
  }

  componentDidMount() {
    wasmC({
      'global': {},
      'env': {
        'memoryBase': 0,
        'tableBase': 0,
        'memory': new WebAssembly.Memory({initial: 256}),
        'table': new WebAssembly.Table({initial: 0, element: 'anyfunc'})
      }
    }).then(result => {
      const exports = result.instance.exports;
      const add = exports.add;
      const fibonacci = exports.fibonacci;
      console.log('C return value was', add(2, 3));
      console.log('Fibonacci', fibonacci(2));
    });

    this.doSomething();
  }

  getExportFunction = async (url) => {
    const env = {
      memoryBase: 0,
      tableBase: 0,
      memory: new WebAssembly.Memory({
        initial: 256
      }),
      table: new WebAssembly.Table({
        initial: 2,
        element: 'anyfunc'
      })
    };
    const instance = await fetch(url).then((response) => {
      return response.arrayBuffer();
    }).then((bytes) => {
      return WebAssembly.instantiate(bytes, {env: env})
    }).then((instance) => {
      return instance.instance.exports;
    });
    return instance;
  };

  fibonacci = (n) => {
    if (n <= 1) {
      return n;
    } else {
      return this.fibonacci(n - 1) + this.fibonacci(n - 2);
    }
  }

  doSomething = async () => {
    const fibonacciUrl = './fibonacci.wasm';
    const {_fibonacci} = await this.getExportFunction(fibonacciUrl);

    this.setState({
      cFibonacci: this.getDuring(_fibonacci),
      jsFibonacci: this.getDuring(this.fibonacci)
    })
  };

  getDuring(func) {
    const start = Date.now();
    func(40);
    return Date.now() - start;
  }

  render() {
    console.log(this.state);
    return (
      <div className="App">
        <header className="App-header">
          <h2>测试计算递归无优化的斐波那契数列性能</h2>
          <h3>当值为 41</h3>
          <span>Javascript实现的斐波那契函数耗费: {this.state.jsFibonacci} ms</span>
          <span>C实现的斐波那契函数耗费: {this.state.cFibonacci} ms</span>
        </header>
      </div>
    );
  }
}

export default App;

# 参考链接

Last Updated: 4/19/2020, 9:58:04 PM