前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Webassembly初识

Webassembly初识

原创
作者头像
CIKEY
发布2019-03-18 10:08:42
1.1K0
发布2019-03-18 10:08:42
举报
文章被收录于专栏:进击的全栈

为何会出现webassembly?

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

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

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

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

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

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

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

代码语言:javascript
复制
function add(a, b) {

    return a + b;

} 
var c = add( 1 + 2);

这时,JIT会编译为

代码语言:javascript
复制
function add(int a, int b) {
    return a + b;
}

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

代码语言:javascript
复制
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干了些什么:

代码语言:javascript
复制
// 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不同位置的数据值

代码语言:javascript
复制
//浮点型数组
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的基本用法:

代码语言:javascript
复制
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实现如下:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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 除了定义了二进制格式以外,还定义了一份对等的文本描述)。上面代码中的二进制码就是以下代码转换的:

代码语言:javascript
复制
int add(int a, int b) {
    return a + b;
}
int square(int a) {
    return a * a;
}

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

代码语言:javascript
复制
(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 一样了。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为何会出现webassembly?
  • 何为asm.js和wasm?
  • 什么是Typed Array?
  • wasm实战
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档