为什么需要 WebAssembly

自从 JavaScript 诞生起到现在已经变成最流行的编程语言,这背后正是 Web 的发展所推动的。Web 应用变得更多更复杂,但这也渐渐暴露出了 JavaScript 的问题:

语法太灵活导致开发大型 Web 项目困难;

性能不能满足一些场景的需要。

针对以上两点缺陷,近年来出现了一些 JS 的代替语言,例如:

微软的 TypeScript 通过为 JS 加入静态类型检查来改进 JS 松散的语法,提升代码健壮性;

谷歌的 Dart 则是为浏览器引入新的虚拟机去直接运行 Dart 程序以提升性能;

火狐的 asm.js 则是取 JS 的子集,JS 引擎针对 asm.js 做性能优化。

以上尝试各有优缺点,其中:

TypeScript 只是解决了 JS 语法松散的问题,最后还是需要编译成 JS 去运行,对性能没有提升;

Dart 只能在 Chrome 预览版中运行,无主流浏览器支持,用 Dart 开发的人不多;

asm.js 语法太简单、有很大限制,开发效率低。

三大浏览器巨头分别提出了自己的解决方案,互不兼容,这违背了 Web 的宗旨; 是技术的规范统一让 Web 走到了今天,因此形成一套新的规范去解决 JS 所面临的问题迫在眉睫。

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

WebAssembly 原理

要搞懂 WebAssembly 的原理,需要先搞懂计算机的运行原理。 电子计算机都是由电子元件组成,为了方便处理电子元件只存在开闭两种状态,对应着 0 和 1,也就是说计算机只认识 0 和 1,数据和逻辑都需要由 0 和 1 表示,也就是可以直接装载到计算机中运行的机器码。 机器码可读性极差,因此人们通过高级语言 C、C++、Rust、Go 等编写再编译成机器码。

由于不同的计算机 CPU 架构不同,机器码标准也有所差别,常见的 CPU 架构包括 x86、AMD64、ARM, 因此在由高级编程语言编译成可自行代码时需要指定目标架构。

WebAssembly 字节码是一种抹平了不同 CPU 架构的机器码,WebAssembly 字节码不能直接在任何一种 CPU 架构上运行, 但由于非常接近机器码,可以非常快的被翻译为对应架构的机器码,因此 WebAssembly 运行速度和机器码接近,这听上去非常像 Java 字节码。

相对于 JS,WebAssembly 有如下优点:

体积小:由于浏览器运行时只加载编译成的字节码,一样的逻辑比用字符串描述的 JS 文件体积要小很多;

加载快:由于文件体积小,再加上无需解释执行,WebAssembly 能更快的加载并实例化,减少运行前的等待时间;

兼容性问题少:WebAssembly 是非常底层的字节码规范,制订好后很少变动,就算以后发生变化,也只需在从高级语言编译成字节码过程中做兼容。可能出现兼容性问题的地方在于 JS 和 WebAssembly 桥接的 JS 接口。

每个高级语言都去实现源码到不同平台的机器码的转换工作是重复的,高级语言只需要生成底层虚拟机(LLVM)认识的中间语言(LLVM IR), LLVM 能实现:

LLVM IR 到不同 CPU 架构机器码的生成;

机器码编译时性能和大小优化。

除此之外 LLVM 还实现了 LLVM IR 到 WebAssembly 字节码的编译功能,也就是说只要高级语言能转换成 LLVM IR,就能被编译成 WebAssembly 字节码,目前能编译成 WebAssembly 字节码的高级语言有:

AssemblyScript:语法和 TypeScript 一致,对前端来说学习成本低,为前端编写 WebAssembly 最佳选择;

c\c++:官方推荐的方式,详细使用见 文档;

Rust:语法复杂、学习成本高,对前端来说可能会不适应。详细使用见 文档;

Kotlin:语法和 Java、JS 相似,语言学习成本低,详细使用见 文档;

Golang:语法简单学习成本低。但对 WebAssembly 的支持还处于未正式发布阶段,详细使用见 文档 。

通常负责把高级语言翻译到 LLVM IR 的部分叫做编译器前端,把 LLVM IR 编译成各架构 CPU 对应机器码的部分叫做编译器后端; 现在越来越多的高级编程语言选择 LLVM 作为后端,高级语言只需专注于如何提供开发效率更高的语法同时保持翻译到 LLVM IR 的程序执行性能。

编写 WebAssembly

AssemblyScript 初体验

接下来详细介绍如何使用 AssemblyScript 来编写 WebAssembly,实现斐波那契序列的计算。 用 TypeScript 实现斐波那契序列计算的模块 f.ts 如下:

export function f(x: i32): i32 {

if (x === 1 || x === 2) {

return 1;

}

return f(x - 1) + f(x - 2)

}Show moreShow more icon

asc f.ts -o f.wasmShow moreShow more icon

就能把以上代码编译成可运行的 WebAssembly 模块。

为了加载并执行编译出的 f.wasm 模块,需要通过 JS 去加载并调用模块上的 f 函数,为此需要以下 JS 代码:

fetch('f.wasm') // 网络加载 f.wasm 文件

.then(res => res.arrayBuffer()) // 转成 ArrayBuffer

.then(WebAssembly.instantiate) // 编译为当前 CPU 架构的机器码 + 实例化

.then(mod => { // 调用模块实例上的 f 函数计算

console.log(mod.instance.f(50));

});Show moreShow more icon

以上代码中出现了一个新的内置类型 i32,这是 AssemblyScript 在 TypeScript 的基础上内置的类型。 AssemblyScript 和 TypeScript 有细微区别,AssemblyScript 是 TypeScript 的子集,为了方便编译成 WebAssembly 在 TypeScript 的基础上加了更严格的 类型限制 ,区别如下:

比 TypeScript 多了很多更细致的内置类型,以优化性能和内存占用,详情 文档;

不能使用 any 和 undefined 类型,以及枚举类型;

可空类型的变量必须是引用类型,而不能是基本数据类型如 string、number、boolean;

函数中的可选参数必须提供默认值,函数必须有返回类型,无返回值的函数返回类型需要是 void;

不能使用 JS 环境中的内置函数,只能使用 AssemblyScript 提供的内置函数 。

总体来说 AssemblyScript 比 TypeScript 又多了很多限制,编写起来会觉得局限性很大; 用 AssemblyScript 来写 WebAssembly 经常会出现 tsc 编译通过但运行 WebAssembly 时出错的情况,这很可能就是你没有遵守以上限制导致的;但 AssemblyScript 通过修改 TypeScript 编译器默认配置能在编译阶段找出大多错误。

AssemblyScript 的实现原理其实也借助了 LLVM,它通过 TypeScript 编译器把 TS 源码解析成 AST,再把 AST 翻译成 IR,再通过 LLVM 编译成 WebAssembly 字节码实现; 上面提到的各种限制都是为了方便把 AST 转换成 LLVM IR。

为什么选 AssemblyScript 作为 WebAssembly 开发语言

AssemblyScript 相对于 C、Rust 等其它语言去写 WebAssembly 而言,好处除了对前端来说无额外新语言学习成本外,还有对于不支持 WebAssembly 的浏览器,可以通过 TypeScript 编译器编译成可正常执行的 JS 代码,从而实现从 JS 到 WebAssembly 的平滑迁移。

接入 Webpack 构建

任何新的 Web 开发技术都少不了构建流程,为了提供一套流畅的 WebAssembly 开发流程,接下来介绍接入 Webpack 具体步骤。

安装以下依赖,以便让 TS 源码被 AssemblyScript 编译成 WebAssembly。

{

"devDependencies": {

"assemblyscript": "github:AssemblyScript/assemblyscript",

"assemblyscript-typescript-loader": "^1.3.2",

"typescript": "^2.8.1",

"webpack": "^3.10.0",

"webpack-dev-server": "^2.10.1"

}

}Show moreShow more icon

修改 webpack.config.js,加入 loader:

module.exports = {

module: {

rules: [

{

test: /\.ts$/,

loader: 'assemblyscript-typescript-loader',

options: {

sourceMap: true,

}

}

]

},

};Show moreShow more icon

修改 TypeScript 编译器配置 tsconfig.json,以便让 TypeScript 编译器能支持 AssemblyScript 中引入的内置类型和函数。

{

"extends": "../../node_modules/assemblyscript/std/portable.json",

"include": [

"./**/*.ts"

]

}Show moreShow more icon

配置直接继承自 assemblyscript 内置的配置文件。

WebAssembly 相关文件格式

前面提到了 WebAssembly 的二进制文件格式 wasm,这种格式的文件人眼无法阅读,为了阅读 WebAssembly 文件的逻辑,还有一种文本格式叫 wast; 以前面讲到的计算斐波那契序列的模块为例,对应的 wast 文件如下:

func $src/asm/module/f (param f64) (result f64)

(local i32)

get_local 0

f64.const 1

f64.eq

tee_local 1

if i32

get_local 1

else

get_local 0

f64.const 2

f64.eq

end

i32.const 1

i32.and

if

f64.const 1

return

end

get_local 0

f64.const 1

f64.sub

call 0

get_local 0

f64.const 2

f64.sub

call 0

f64.add

endShow moreShow more icon

这和汇编语言非常像,里面的 f64 是数据类型,f64.eq f64.sub f64.add 则是 CPU 指令。

为了把二进制文件格式 wasm 转换成人眼可见的 wast 文本,需要安装 WebAssembly 二进制工具箱 WABT , 在 Mac 系统下可通过 brew install WABT 安装,安装成功后可以通过命令 wasm2wast f.wasm 获得 wast;除此之外还可以通过 wast2wasm f.wast -o f.wasm 逆向转换回去。

WebAssembly 相关工具

除了前面提到的 WebAssembly 二进制工具箱,WebAssembly 社区还有以下常用工具:

Emscripten: 能把 C、C++代码转换成 wasm、asm.js;

Binaryen: 提供更简洁的 IR,把 IR 转换成 wasm,并且提供 wasm 的编译时优化、wasm 虚拟机,wasm 压缩等功能,前面提到的 AssemblyScript 就是基于它。

WebAssembly JS API

目前 WebAssembly 只能通过 JS 去加载和执行,但未来在浏览器中可以通过像加载 JS 那样 去加载和执行 WebAssembly,下面来详细介绍如何用 JS 调 WebAssembly。

JS 调 WebAssembly 分为 3 大步: 加载字节码 > 编译字节码 > 实例化 ,获取到 WebAssembly 实例后就可以通过 JS 去调用了,以上 3 步具体的操作是:

对于浏览器可以通过网络请求去加载字节码,对于 Nodejs 可以通过 fs 模块读取字节码文件;

在获取到字节码后都需要转换成 ArrayBuffer 后才能被编译,通过 WebAssembly 通过的 JS API WebAssembly.compile 编译后会通过 Promise resolve 一个 WebAssembly.Module ,这个 module 是不能直接被调用的需要;

在获取到 module 后需要通过 WebAssembly.Instance API 去实例化 module,获取到 Instance 后就可以像使用 JS 模块一个调用了。

其中的第 2、3 步可以合并一步完成,前面提到的 WebAssembly.instantiate 就做了这两个事情。

WebAssembly.instantiate(bytes).then(mod=>{

mod.instance.f(50);

})Show moreShow more icon

WebAssembly 调 JS

之前的例子都是用 JS 去调用 WebAssembly 模块,但是在有些场景下可能需要在 WebAssembly 模块中调用浏览器 API,接下来介绍如何在 WebAssembly 中调用 JS。

WebAssembly.instantiate 函数支持第二个参数 WebAssembly.instantiate(bytes,importObject),这个 importObject 参数的作用就是 JS 向 WebAssembly 传入 WebAssembly 中需要调用 JS 的 JS 模块。举个具体的例子,改造前面的计算斐波那契序列在 WebAssembly 中调用 Web 中的 window.alert 函数把计算结果弹出来,为此需要改造加载 WebAssembly 模块的 JS 代码:

WebAssembly.instantiate(bytes,{

window:{

alert:window.alert

}

}).then(mod=>{

mod.instance.f(50);

})Show moreShow more icon

对应的还需要修改 AssemblyScript 编写的源码:

// 声明从外部导入的模块类型

declare namespace window {

export function alert(v: number): void;

}

function _f(x: number): number {

if (x == 1 || x == 2) {

return 1;

}

return _f(x - 1) + _f(x - 2)

}

export function f(x: number): void {

// 直接调用 JS 模块

window.alert(_f(x));

}Show moreShow more icon

修改以上 AssemblyScript 源码后重新用 asc 通过命令 asc f.ts 编译后输出的 wast 文件比之前多了几行:

(import "window" "alert" (func $src/asm/module/window.alert (type 0)))

(func $src/asm/module/f (type 0) (param f64)

get_local 0

call $src/asm/module/_f

call $src/asm/module/window.alert)Show moreShow more icon

多出的这部分 wast 代码就是在 AssemblyScript 中调用 JS 中传入的模块的逻辑。

除了以上常用的 API 外,WebAssembly 还提供一些 API,你可以通过这个 d.ts 文件 去查看所有 WebAssembly JS API 的细节。

不止于浏览器

WebAssembly 作为一种底层字节码,除了能在浏览器中运行外,还能在其它环境运行。

直接执行 wasm 二进制文件

前面提到的 Binaryen 提供了在命令行中直接执行 wasm 二进制文件的工具,在 Mac 系统下通过 brew install binaryen 安装成功后,通过 wasm-shell f.wasm 文件即可直接运行。

在 Node.js 中运行

目前 V8 JS 引擎已经添加了对 WebAssembly 的支持,Chrome 和 Node.js 都采用了 V8 作为引擎,因此 WebAssembly 也可以运行在 Node.js 环境中;

V8 JS 引擎在运行 WebAssembly 时,WebAssembly 和 JS 是在同一个虚拟机中执行,而不是 WebAssembly 在一个单独的虚拟机中运行,这样方便实现 JS 和 WebAssembly 之间的相互调用。

要让上面的例子在 Node.js 中运行,可以使用以下代码:

const fs = require('fs');

function toUint8Array(buf) {

var u = new Uint8Array(buf.length);

for (var i = 0; i < buf.length; ++i) {

u[i] = buf[i];

}

return u;

}

function loadWebAssembly(filename, imports) {

// 读取 wasm 文件,并转换成 byte 数组

const buffer = toUint8Array(fs.readFileSync(filename));

// 编译 wasm 字节码到机器码

return WebAssembly.compile(buffer)

.then(module => {

// 实例化模块

return new WebAssembly.Instance(module, imports)

})

}

loadWebAssembly('../temp/assembly/module.wasm')

.then(instance => {

// 调用 f 函数计算

console.log(instance.exports.f(10))

});Show moreShow more icon

在 Nodejs 环境中运行 WebAssembly 的意义其实不大,原因在于 Nodejs 支持运行原生模块,而原生模块的性能比 WebAssembly 要好。 如果你是通过 C、Rust 去编写 WebAssembly,你可以直接编译成 Nodejs 可以调用的原生模块。

WebAssembly 展望

从上面的内容可见 WebAssembly 主要是为了解决 JS 的性能瓶颈,也就是说 WebAssembly 适合用于需要大量计算的场景,例如:

在浏览器中处理音视频, flv.js 用 WebAssembly 重写后性能会有很大提升;

React 的 dom diff 中涉及到大量计算,用 WebAssembly 重写 React 核心模块能提升性能。Safari 浏览器使用的 JS 引擎 JavaScriptCore 也已经支持 WebAssembly,RN 应用性能也能提升;

结束语

WebAssembly 标准虽然已经定稿并且得到主流浏览器的实现,但目前还存在以下问题:

浏览器兼容性不好,只有最新版本的浏览器支持,并且不同的浏览器对 JS WebAssembly 互调的 API 支持不一致;

生态工具不完善不成熟,目前还不能找到一门体验流畅的编写 WebAssembly 的语言,都还处于起步阶段;

学习资料太少,还需要更多的人去探索去踩坑。

总之现在的 WebAssembly 还不算成熟,如果你的团队没有不可容忍的性能问题,那现在使用 WebAssembly 到产品中还不是时候, 因为这可能会影响到团队的开发效率,或者遇到无法轻易解决的坑而阻塞开发。

webassembly类型_WebAssembly 现状与实战相关推荐

  1. webassembly类型_WebAssembly 那些事儿

    WebAssembly 那些事儿 什么是 WebAssembly? WebAssembly 是除 JavaScript 以外,另一种可以在网页中运行的编程语言,并且相比之下在某些功能和性能问题上更具优 ...

  2. webassembly类型_几张图让你看懂WebAssembly

    (图片来源:giphy.com) 编者按:本文由明非在众成翻译平台上翻译. 最近,WebAssembly 在 JavaScript 圈非常的火!人们都在谈论它多么多么快,怎样怎样改变 Web 开发领域 ...

  3. android 选择银行类型,『自定义View实战』—— 银行种类选择器

    在工作中难免遇到自定义 View 的相关需求,本身这方面比较薄弱,因此做个记录,也是自己学习和成长的积累.自定义View实战 前言 年前的最后一个开发需求,将之前H5开卡界面转变成native.意思就 ...

  4. mysql实现宠物类型的查询_JDBC实战案例--利用jdbc实现的宠物信息管理系统

    1 packagecom.daliu.jdbc;2 3 importjava.sql.Connection;4 importjava.sql.ResultSet;5 importjava.sql.SQ ...

  5. WebAssembly入门-未来可能发生的巨变

    引言 先看下 官网 给的定义. WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virt ...

  6. 3分钟了解 WebAssembly

    3分钟了解 WebAssembly 一.概念 二.WebAssembly 使用 三.JavaScript 和 WebAssembly 四.WebAssembly 现状 一.概念 WebAssembly ...

  7. 初学者的机器学习入门实战教程!

    点击上方↑↑↑蓝字关注我们~ 「2019 Python开发者日」,购票请扫码咨询 ↑↑↑ 作者 | Adrian Rosebrock 译者 | kbsc13,京东算法工程师,研究领域计算机视觉 来源 ...

  8. 【elasticsearch】Elasticsearch 空值处理实战

    1.概述 转载:Elasticsearch 空值处理实战指南 并且进行实操. 1.引言 实战业务场景中,经常会遇到定义空值.检索指定空值数据的情况. 这时候,当我们翻看官方文档 null_value ...

  9. 现代C++新特性 强枚举类型(PC浏览效果更佳)

    文字版PDF文档链接:现代C++新特性(文字版)-C++文档类资源-CSDN下载 1. 枚举类型的弊 C++之父本贾尼·斯特劳斯特卢普曾经在他的The Design And Evolution Of ...

最新文章

  1. 1.6 开发集和测试集的大小-深度学习第三课《结构化机器学习项目》-Stanford吴恩达教授
  2. Python编程基础:第三节 字符串方法String Methods
  3. (内联元素和块级元素)
  4. pwm调速流程图小车_PWM调速+循迹__智能小车程序
  5. python docx 复制_python制作单词抽测题(word版)(一)
  6. java怎么安装_Java怎么安装?Java运行环境安装教程
  7. 网页连接数据库 服务器,关于asp网页连接远程服务器上数据库问题
  8. 自动垃圾回收学习笔记-垃圾回收算法
  9. 44. Element insertBefore() 方法
  10. 基于51单片机的四通道交通灯设计
  11. 超出superView的subview响应方法
  12. 微信发红包的测试用例点
  13. 使用阿里云实现简单的直播
  14. 本人秋招结束了,愿所有人都拿到满意的offer
  15. 【LeetCode844.比较含退格的字符串】——双指针法
  16. Google 宣布废弃 LiveData.observe 方法
  17. 领域搜索算法java_使用JAVA实现算法——禁忌搜索算法解决TSP问题
  18. 广州品向科技:浅析汽车app手机软件的发展
  19. gulp-Gulp资料大全:入门、插件、脚手架、包清单
  20. 微信开发限制页面pc端登陆

热门文章

  1. react native ios 上架
  2. TP5 急速上手 语法规则
  3. Educational Codeforces Round 39 G Almost Increasing Array
  4. 设计模式-15-建造者模式
  5. 【ADO.NET--MVC】初学MVC(MVC入门)(1)
  6. Nancy 学习-进阶部分 继续跨平台
  7. 必应输入法(桌面版)软件分析和用户需求调查
  8. uboot环境变量及常用命令
  9. why do we use process keys
  10. ACMMM 2021《LSG》性能SOTA!用GNN和GAN的方式来强化Video Captioning的学习!