Webassembly初识

为何会出现webassembly?

javascript自从被创造开始就吐槽不断,它确实也埋下了不少的坑。

首先,它是一种解释性语言,大神最开始的设计目标用户就是“非专业编程人员和设计师”,避免了非专业人士对编译器了解的需要,解释性语言就是边解释边执行,与编译性语言的先编译后执行相比,执行速度慢了很多;

其次,javascript中没有类型,因为学习类型就需要学习cpu啦。有类型的语言在编译生成本地代码的过程中,就已经确定了其变量地址和类型,运行本地代码时通过数组和位移就可以存取变量和方法,不需要额外的查找,但是无类型语言就需要临时确定,每次执行需要重新确定变量存储栈区的变量标志符、变量值或地址、堆区存储的对象;

再次,对象模型也比较奇葩,没有泛型、缺省参数这些。

这些直接导致的就是性能问题,于是就是开始了漫长的填坑过程。

v8引擎的JIT,在代码执行的前一刻,引擎会编译需要运行的代码,v8更加直接的将抽象语法树通过JIT 技术转换成本地代码,由此保证了执行速度。

但是!JIT依然跨不过无类型语言的坑,煮个栗子:

function add(a, b) {

    return a + b;

} 
var c = add( 1 + 2);

这时,JIT会编译为

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

但是若遇到以下情况JIT就只能重新编译一遍

var d = add('1' + '2');

为了解决无类型语言的障碍,我们可以把变量类型标注出来就好啦!于是就有了我们常用的TypeScript和JSX(强类型语言),最后再编译成弱类型语言,但保证了同一变量或方法的类型不会变来变去。

另外一个比较火的是火狐的asm.js,利用 | & << >>等符号来标志变量的类型,这样编译器就不需要猜类型了。

何为asm.js和wasm?

asm是mozilla提出的一套基于JS的语法标准,所以它是javascript的一个子集。主要是由Emscrpiten项目催生出来的,目的是解决js的执行效率问题。

但是实际上asm.js只能处理几种数值类型,对于字符串和布尔型变量没有做处理。JS语言不仅是弱类型的,而且数值类型只有一种-Number,Number类型的数据采用双精度64位格式的IEEE 754值表示.我们从代码角度看下asm干了些什么:

// c程序:
char xInt8 = 127;
char yInt8 = xInt8 + 1; // 溢出:yInt8 == (char) -128
char zInt8 = xInt8 / 2; // 舍入:zInt8 == (char) 63

上面这段代码通过JS模拟后的代码如下:

var xInt8 = 127; // (1) var $add = (xInt8 + 1) | 0; // (2) var yInt8 = ($add << 24) >> 24; // (3) var $div = ((xInt8 | 0) / 2) & -1; // (4) var zInt8 = ($div << 24) >> 24; // (5)

(2)“| 0”告诉js引擎这里的标识符是个integer,它可以帮助JIT编译器生成integer相应的机器代码。这里也是asm的一个关键点:类型注释。

(3)先左移24位再右移24位,让第8位成为32位整数的符号位,来模拟8位整数计算,此时yInt8 == -128

(4)js中127/2结果是浮点数63.5,利用X &-1将结果转化成32位整数63

补充知识点:

<< 左移,>> 有符号右移, >>> 无符号右移,二进制位运算符(<<, >>, >>>, |, ^, ~, &, !)等都是将int -> int,即将操作数识别为integer。

于是,利用一些位移和逻辑运算可以模拟C/C++语言中的数据计算,Emscripten就利用这个方法将C代码转换成JS代码。另外一种更为简单的方法是,对Typed Array元素赋值则会自动进行相应的溢出和舍入处理。

什么是Typed Array?

它是H5标准与性能之一,主要是为了弥补js处理二进制格式数据的不足,利用Typed Array可以非常方便地操作二进制的数据(例如二进制的文件、网络数据等等),固定类型数值的计算加速,或者实现类似C的struct和union的功能。

Typed Array主要由下面几个类构成:

ArrayBuffer: 连续的内存缓冲区,用于实际储存各种类型的数组数据

Typed Array View类:比如Int32Array、Uint8Array、Float32Array等,表示一个特定类型的数组

DataView: 工具类,提供getUint8、setFloat32等工具方法修改ArrayBuffer不同位置的数据值

//浮点型数组
var f64 = new Float64Array(8);
var f32 = new Float32Array(16);

//有符号整型数组
var i32 = new Int32Array(16);
var i16 = new Int16Array(32);
var i8  = new Int8Array(64);

//无符号整型数组
var u32 = new Uint32Array(16);
var u16 = new Uint16Array(32);
var u8  = new Uint8Array(64);
var pixels = new Uint8ClampedArray(64);

//等效处理: 专门为Canvas img像素处理运算设计
u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));
pixels[i] *= gamma;

另外,每个Typed Array类的对象内部都指向一个ArrayBuffer,多个Typed Array对象可以共享同一个ArrayBuffer的缓冲区,我们下面来看一下Typed Array的基本用法:

var b = new ArrayBuffer(8);
var v1 = new Int32Array(b);
var v2 = new Uint8Array(b, 2);
// 创建v3指向b,16位整型,从2字节开,长度为2
var v3 = new Int16Array(b, 2, 2);

以上变量在内存中的存储关系如下:

所以之前的c运算转换为用Typed Array实现如下:

var a = new Int8Array(3)
a[0] = 127
a[1] = a[0] + 1
a[2] = a[0] / 2

wasm实战

第一步要安装支持wasm的浏览器,体验新技术,建议使用激进版浏览器,最新版本中都已经支持了 WebAssembly。除了激进浏览器,在主流版本里开启 flag 也是可以使用 WebAssembly 的:

Chrome: 打开 chrome://flags/#enable-webassembly,选择 enable。

Firefox: 打开 about:config 将 javascript.options.wasm 设置为 true。

大家可以用一下代码试试自己的浏览器是否支持webassembly:

WebAssembly.compile(new Uint8Array([0,97,115,109,1,0,0,0,1,140,128,
128,128,0,2,96,2,127,127,1,127,96,1,127,1,127,3,131,128,128,128,0,2,
0,1,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,
128,128,0,0,7,153,128,128,128,0,3,6,109,101,109,111,114,121,2,0,3,97,100,
100,0,0,6,115,113,117,97,114,101,0,1,10,153,128,128,128,0,2,135,128,128,
128,0,0,32,1,32,0,106,11,135,128,128,128,0,0,32,0,32,0,108,11])).then(module => {
  //WebAssembly.Instance 将模块对象转成 WebAssembly 实例
  const instance = new WebAssembly.Instance(module)
  //通过 instance.exports 可以拿到 wasm 代码输出的接口,剩下的代码就和和普通 javascript 一样了。
  const { add, square } = instance.exports

  console.log('2 + 4 =', add(2, 4))
  console.log('3^2 =', square(3))
  console.log('(2 + 5)^2 =', square(add(2 + 5)))

  //需要注意数据类型
  console.log(square('Tom'))
  console.log(add(2e+66, 3e+66))
})

第二步编译工具,wasm.js二进制文件一般都不是手写,而是由C/C++编译转换而来,常用的关键工具就是Emscripten,可以将 C/C++ 编译成 asm.js,使用 WASM 标志也可以直接生成 WebAssembly 二进制文件(后缀是 .wasm)。这里有个在线转换工具可以试试,该工具还可以直接生成二进制文件,和.wast文件(WebAssembly 除了定义了二进制格式以外,还定义了一份对等的文本描述)。上面代码中的二进制码就是以下代码转换的:

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

它转换成的.wast文件如下:

(module
 (table 0 anyfunc)
 (memory $0 1)
 (export "memory" (memory $0))
 (export "add" (func $add))
 (export "square" (func $square))
 (func $add (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
  (i32.add
   (get_local $1)
   (get_local $0)
  )
 )
 (func $square (; 1 ;) (param $0 i32) (result i32)
  (i32.mul
   (get_local $0)
   (get_local $0)
  )
 )
)

第三步熟悉使用webassembly的js api,传送门

上面的测试代码就用到了几个常用的js api: WebAssembly.compile 返回 Promise对象,里面的代码是ArrayBuffer二进制。resolve方法的参数module模块对象即为WebAssembly.Module的实例;使用 WebAssembly.Instance 将模块对象转成 WebAssembly 实例(第二个参数可以用来导入变量)。通过 instance.exports 可以拿到 wasm 代码输出的接口,剩下的代码就和和普通 javascript 一样了。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Fundebug

高效开发者是如何个性化VS Code插件与配置的?

2年之前,我放弃了Sublime Text,选择了Visual Studio Code作为代码编辑器。

21250
来自专栏小狼的世界

Composer使用体验

简单来说,Composer是PHP的包依赖管理器。但是Composer并不是类似于Yum、Apt的包管理器。Composer可以用于包或者第三方库的安装,但是可...

8020
来自专栏Jerry的SAP技术分享

SAP 前端技术的演化史简介

Jerry之前曾经写过一篇微信公众号文章,题目叫<<SAP UI和Salesforce UI开发漫谈>>

10420
来自专栏三流程序员的挣扎

Dart 笔记 1 - 基础

Dart 没有 public、protected 和 private 的关键字。如果标识符以下划线 _ 开头,编译器会将其强制标记为库私有的。

13130
来自专栏杨飞@益术

JAVA初中级程序员笔试试题

(多选题) 1.以下哪些不是Java保留字__________ A. private     B. Final    C. class    D. Thro...

31720
来自专栏Android知识分享

Android 多线程:这是一份详细的AsyncTask使用教程

下面,我将用1个实例讲解 具体如何使用 `AsyncTask`

18130
来自专栏一个会写诗的程序员的博客

Selenium Webdriver 在当前浏览器 Context 中 执行 JS 脚本。 Execute JavaScript based code using Selenium Webdriver

In Selenium Webdriver, locators like XPath, CSS, etc. are used to identify and p...

17410
来自专栏大眼瞪小眼

Compoer介绍

Composer 是 PHP 的一个依赖管理工具。它允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们。

10520
来自专栏杨飞@益术

jquery要怎么写才能速度最快?(转…

1. 使用最新版本的jQuery jQuery的版本更新很快,你应该总是使用最新的版本。因为新版本会改进性能,还有很多新功能。 下面就来看看,不...

8530
来自专栏杨飞@益术

关于JS倒计时

<script type="text/javascript" language="javascript">         function Curr...

19030

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励