写点什么

WebAssembly 完全入门:了解 wasm 的前世今身

  • 2019-10-22
  • 本文字数:4957 字

    阅读完需:约 16 分钟

WebAssembly完全入门:了解wasm的前世今身

接触 WebAssembly 之后,在 google 上看了很多资料。然而,这些资料对 WebAssembly 的使用、介绍、意义等方面的解释都比较模糊和笼统,总是令人感觉没有获得预期的收获,要么是因为文章中的例子自己去实操不能成功,要么就是不知所云、一脸蒙蔽。本着业务催生技术的态度,这篇文章就此诞生了。本文主要是对 WebAssembly 的背景做一些介绍,包括 WebAssembly 是怎么出现的?优势在哪儿?等等。

WebAssembly 是什么?

定义

首先我们给它下个定义。


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

例子

当然,我知道,即使你看了定义也不知道 WebAssembly 到底是什么东西。废话不多说,我们通过一个简单的例子来看看 WebAssembly 到底是什么。



上图的左侧是用 C++ 实现的求递归的函数。中间是十六进制的 Binary Code。右侧是指令文本。可能有人就问,这跟 WebAssembly 有个屁的关系?其实,中间的十六进制的 Binary Code 就是 WebAssembly。

编译目标

大家可以看到,其可写性和可读性差到无法想象。那是因为 WebAssembly 不是用来给各位用手 一行一行撸 的代码,WebAssembly 是一个 编译目标。什么是编译目标?当我们写 TypeScript 的时候,Webpack 最后打包生成的 JavaScript 文件就是编译目标。可能大家已经猜到了,上图的 Binary 就是左侧的 C++ 代码经过编译器编译之后的结果。

WebAssembly 的由来

性能瓶颈

在业务需求越来越复杂的现在,前端的开发逻辑越来越复杂,相应的代码量随之变的越来越多。相应的,整个项目的起步的时间越来越长。在性能不好的电脑上,启动一个前端的项目甚至要花上十多秒。这些其实还好,说明前端越来越受到重视,越来越多的人开始进行前端的开发。


但是除了逻辑复杂、代码量大,还有另一个原因是 JavaScript 这门语言本身的缺陷,JavaScript 没有静态变量类型。这门解释型编程语言的作者 Brendan Eich,仓促的创造了这门如果被广泛使用的语言,以至于 JavaScript 的发展史甚至在某种层面上变成了填坑史。为什么说没有静态类型会降低效率。这会涉及到一些 JavaScript 引擎的一些知识。

静态变量类型所带来的问题


这是 Microsoft Edge 浏览器的 JavaScript 引擎 ChakraCore 的结构。我们来看一看我们的 JavaScript 代码在引擎中会经历什么。- JavaScript 文件会被下载下来。


  • 然后进入 Parser,Parser 会把代码转化成 AST(抽象语法树)。

  • 然后根据抽象语法树,Bytecode Compiler 字节码编译器会生成引擎能够直接阅读、执行的字节码。

  • 字节码进入翻译器,将字节码一行一行的翻译成效率十分高的 Machine Code。


在项目运行的过程中,引擎会对执行次数较多的 function 记性优化,引擎将其代码编译成 Machine Code 后打包送到顶部的 Just-In-Time(JIT) Compiler,下次再执行这个 function,就会直接执行编译好的 Machine Code。但是由于 JavaScript 的动态变量,上一秒可能是 Array,下一秒就变成了 Object。那么上一次引擎所做的优化,就失去了作用,此时又要再一次进行优化。

asm.js 出现

所以为了解决这个问题,WebAssembly 的前身,asm.js 诞生了。asm.js 是一个 Javascript 的严格子集,合理合法的 asm.js 代码一定是合理合法的 JavaScript 代码,但是反之就不成立。同 WebAssembly 一样,asm.js 不是用来给各位用手 一行一行撸 的代码,asm.js 是一个 编译目标。它的可读性、可读性虽然比 WebAssembly 好,但是对于开发者来说,仍然是无法接受的。


asm.js 强制静态类型,举个例子。


function asmJs() {    'use asm';
let myInt = 0 | 0; let myDouble = +1.1;}
复制代码


为什么 asm.js 会有静态类型呢?因为像0 | 0这样的,代表这是一个 Int 的数据,而+1.1则代表这是一个 Double 的数据。

asm.js 不能解决所有的问题

可能有人有疑问,这问题不是解决了吗?那为什么会有 WebAssembly?WebAssembly 又解决了什么问题?大家可以再看一下上面的 ChakraCore 的引擎结构。无论 asm.js 对静态类型的问题做的再好,它始终逃不过要经过 Parser,要经过 ByteCode Compiler,而这两步是 JavaScript 代码在引擎执行过程当中消耗时间最多的两步。而 WebAssembly 不用经过这两步。这就是 WebAssembly 比 asm.js 更快的原因。

WebAssembly 横空出世

所以在 2015 年,我们迎来了 WebAssembly。WebAssembly 是经过编译器编译之后的代码,体积小、起步快。在语法上完全脱离 JavaScript,同时具有沙盒化的执行环境。WebAssembly 同样的强制静态类型,是 C/C++/Rust 的编译目标。

WebAssembly 的优势

WebAssembly 和 asm.js 性能对比

下面的图是 Unity WebGL 使用和不使用 WebAssembly 的起步时间对比的一个 BenchMark,给大家当作一个参考。可以看到,在 FireFox 中,WebAssembly 和 asm.js 的性能差异达到了 2 倍,在 Chrome 中达到了 3 倍,在 Edge 中甚至达到了 6 倍。通过这些对比也可以从侧面看出,目前所有的主流浏览器都已经支持 WebAssembly V1(Node >= 8.0.0)。


与 JavaScript 做对比

我自己在一个用create-react-app新建的项目中,分别对比了 WebAssembly 版本和原生 JavaScript 版本的递归无优化的 Fibonacci 函数,下图是这两个函数在值是 45、48、50 的时候的性能对比。



看图说话,这就是 WebAssembly 与 JavaScript 很实际的一个性能对比。几乎稳定的是 JavaScript 的两倍。

WebAssembly 在大型项目中的应用

在这里能够举的例子还是很多,比如 AutoCAD、GoogleEarth、Unity、Unreal、PSPDKit、WebPack 等等。拿其中几个来简单说一下。

AutoCAD

这是一个用于画图的软件,在很长的一段时间是没有 Web 的版本的,原因有两个,其一,是 Web 的性能的确不能满足他们的需求。其二,在 WebAssembly 没有面世之前,AutoCAD 是用 C++ 实现的,要将其搬到 Web 上,就意味着要重写他们所有的代码,这代价十分的巨大。


而在 WebAssembly 面世之后,AutoCAD 得以利用编译器,将其沉淀了 30 多年的代码直接编译成 WebAssembly,同时性能基于之前的普通 Web 应用得到了很大的提升。正是这些原因,得以让 AutoCAD 将其应用从 Desktop 搬到 Web 中。

Google Earth

Google Earth 也就是谷歌地球,因为需要展示很多 3D 的图像,对性能要求十分高,所以采取了一些 Native 的技术。最初的时候就连 Google Chrome 浏览器都不支持 Web 的版本,需要单独下载 Google Earth 的 Destop 应用。而在 WebAssembly 之后呢,谷歌地球推出了 Web 的版本。而据说下一个可以运行谷歌地球的浏览器是 FireFox。

Unity 和 Unreal 游戏引擎

这里给两个油管的链接自己体验一下。


WebAssembly 要取代 JavaScript?

答案是否定的,请看下图。



大家可以看到这是一个协作关系。WebAssembly 是被设计成 JavaScript 的一个完善、补充,而不是一个替代品。WebAssembly 将很多编程语言带到了 Web 中。但是 JavaScript 因其不可思议的能力,仍然将保留现有的地位。

什么时候使用 WebAssembly?

说了这么多,我到底什么时候该使用它呢?总结下来,大部分情况分两个点。


  • 对性能有很高要求的 App/Module/ 游戏。

  • 在 Web 中使用 C/C++/Rust/Go 的库举个简单的例子。如果你要实现的 Web 版本的 Ins 或者 Facebook, 你想要提高效率。那么就可以把其中对图片进行压缩、解压缩、处理的工具,用 C++ 实现,然后再编译回 WebAssembly。

WebAssembly 的几个开发工具

WebAssembly 的意义

在我的个人理解上,WebAssembly 并没有要替代 JavaScript,一统天下的意思。我总结下来就两个点。


  • 给了 Web 更好的性能。

  • 给了 Web 更多的可能关于 WebAssembly 的性能问题,之前也花了很大的篇幅讲过了。而更多的可能,随着 WebAssembly 的技术越来越成熟,势必会有更多的应用,从 Desktop 被搬到 Web 上,这会使本来已经十分强大的 Web 更加丰富和强大。

WebAssembly 实操

要进行这个实际操作,你需要安装上文提到过的编译器 Emscripten,然后按照 这个步骤去安装。以下的步骤都默认为你已经安装了 Emscripten。


安装步骤: http://webassembly.org.cn/getting-started/developers-guide/

WebAssembly 在 Node 中的应用

导入 Emscripten 环境变量

进入到你的 emscripten 安装目录,执行以下代码。


source emsdk/emsdk_env.sh
复制代码

新建 C 文件

用 C 实现一个求和文件test.c,如下。


int add(int a, int b) {    return a + b;}
复制代码

使用 Emscripten 编译 C 文件

在同样的目录下执行如下代码。


emcc test.c -Os -s WASM=1 -s SIDE_MODULE=1 -o test.wasm
复制代码


emcc就是 Emscripten 编译器,test.c是我们的输入文件,-Os表示这次编译需要优化,-s WASM=1表示输出 wasm 的文件,因为默认的是输出 asm.js,-s SIDE_MODULE=1表示就只要这一个模块,不要给我其他乱七八糟的代码,-o test.wasm是我们的输出文件。


编译成功之后,当前目录下就会生成test.wasm

编写在 Node 中调用的代码

新建一个 js 文件test.js。代码如下。


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));
复制代码

执行 test.js

运行以下代码。


node test.js
复制代码


然后就可以看到输出的结果 109 了。

WebAssembly 在 React 当中的应用

通过 fetch 的方法调用 直接用 fetch 的方式。大概的调用方式如下。


const fibonacciUrl = './fibonacci.wasm';const {_fibonacci} = await this.getExportFunction(fibonacciUrl);
复制代码


getExportFunction具体代码如下。


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;};
复制代码

通过 import C 文件来调用

先通过 Import 的方式来引进依赖。


import wasmC from './add.c';
复制代码


然后进行调用。具体的方式如下。


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, 5643));  console.log('Fibonacci', fibonacci(2));});
复制代码


详细的代码在这里: https://github.com/detectiveHLH/webassembly-in-react

写在后面

如今技术出现的越来越多,但是实际上在工作中能够用到的,越并不是那么多。其实很多大厂所输出的一些技术,都是有业务场景的,有业务做推动。而不是凭空造轮子。所以总结下来适合自己的才是最好的。当然不是说不要了解新技术,了解新技术跟上步伐是十分必要的。我们现在不用,不代表不需要了解。相反,以后再遇到类似的业务场景时,我们就会多一种选择,可以更加从容的对待。


本文转载自微信公众号:SH 的全栈笔记


2019-10-22 18:5518695

评论 2 条评论

发布
用户头像
请问kotlin/native什么时候支持wasm
2019-10-25 22:41
回复
现在支持了
2023-04-23 10:19 · 上海
回复
没有更多了
发现更多内容

具身智能领域,全球Top50国/华人图谱(含具身智能赛道“师徒关系图”)

机器人头条

科技 大模型 人形机器人 具身智能

K8s进阶之一文搞懂PV,PVC及SC

电子尖叫食人鱼

Kubernetes 容器

大型跨国企业搭建企业网络需要注意哪些方面?

Ogcloud

企业组网 企业网络 跨国网络 跨国企业组网

如何通过DNS解析实现负载均衡?

防火墙后吃泡面

组建小型局域网全攻略:6步搞定设备选择与网络搭建

Ogcloud

组网 局域网 企业组网 公司网络 局域网搭建

高新技术加持下,低代码平台还能进化成什么样?

天津汇柏科技有限公司

低代码

【有奖活动】 放“码”来战,端云一体化挑战赛

HarmonyOS SDK

harmoyos

远控安全金标准,ToDesk、向日葵、网易UU安全功能盘点,是否能攻破防线

小喵子

远程办公 远程 远程控制软件

Java 原生异步编程与Spring 异步编程 详解

不在线第一只蜗牛

Java spring

AI 调教指南!一文教会你如何在 Trae IDE 中配置自定义规则

火山引擎开发者社区

AI 火山引擎

2025年PM产品力领航者大会 | 上海巅峰论道

新消费日报

HarmonyOS沙箱文件管理与离线包加载机制解析

记忆深处的声音

鸿蒙 开发工具 HarmonyOS HarmonyOS NEXT 实践分享

「智元机器人」“稚晖君”彭志辉:机器人不玩“后空翻”,选择实用主义优先路线!

机器人头条

科技 大模型 人形机器人 具身智能

热点追踪+精准解读——火山引擎 AI 域名推荐上线!

火山引擎边缘云

域名解析 域名 域名备案 域名系统 AI Agent,

遭遇DDoS攻击为什么不能反击回去?

网络安全服务

CDN 服务器 DDoS 带宽 高防IP

【免费开源】积木报表MongoDB数据集使用实战

JEECG低代码

mongodb 数据可视化 报表 报表工具

推理加速新范式:火山引擎高性能分布式 KVCache (EIC)核心技术解读

火山引擎开发者社区

火山引擎

2025链游爆款方法论:从Axie到AI跨链的破局路径

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 代币开发 代币开发公链开发

大型网站架构实战!

程序员高级码农

Java 程序员 架构师

veMLP x veRL :玩转强化学习训练

火山引擎开发者社区

开发 火山引擎

全国首个!字节跳动发布 EthLink,填补以太网 GPU Scale-up 互联协议空白

火山引擎开发者社区

字节跳动 以太网

RocketMQ半消息对消费者不可见是如何实现的?——事务消息机制揭秘

量贩潮汐·WholesaleTide

Java RocketMQ

十年一诺:一张SPN获奖证书背后的技术征途

脑极体

通信

【浪潮海岳inDatax数据中台专栏】海岳inDataX“易宝特”流程自动化平台建设与实践

inBuilder低代码平台

如何在通义灵码里使用 MCP 能力?

阿里云云效

阿里云 通义灵码 MCP

区块链钱包开发全解析:从架构设计到安全生态构建

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 公链开发 代币开发

重磅预告|开源鸿蒙开发者大会2025举办在即

最新动态

Python 3.14 新特性盘点,更新了些什么?

不在线第一只蜗牛

Python

混合编程会是软件产业发展的必然选择吗?

代码制造者

混合编程

集成指南:如何基于融云 Flutter IMKit 实现双端丝滑社交体验

融云 RongCloud

如何在通义灵码里使用 MCP 能力?

阿里巴巴云原生

阿里云 云原生 通义灵码 MCP

WebAssembly完全入门:了解wasm的前世今身_大前端_SH的全栈笔记_InfoQ精选文章