1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-24 10:22:23 +00:00
es6tutorial/docs/simd.md
2016-10-03 22:46:08 +08:00

462 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SIMD 的用法
## 概述
SIMD发音`/sim-dee/`是“Single Instruction/Multiple Data”的缩写意为“单指令多数据”。它是JavaScript操作CPU对应指令的接口你可以看做这是一种不同的运算执行模式。与它相对的是SISD“Single Instruction/Single Data”即“单指令单数据”。
SIMD的含义是使用一个指令完成多个数据的运算SISD的含义是使用一个指令完成单个数据的运算这是JavaScript的默认运算模式。显而易见SIMD的执行效率要高于SISD所以被广泛用于3D图形运算、物理模拟等运算量超大的项目之中。
为了理解SIMD请看下面的例子。
```javascript
var a = [1, 2, 3, 4];
var b = [5, 6, 7, 8];
var c = [];
c[0] = a[0] + b[0];
c[1] = a[1] + b[1];
c[2] = a[2] + b[2];
c[3] = a[3] + b[3];
c // Array[6, 8, 10, 12]
```
上面代码中,数组`a``b`的对应成员相加,结果放入数组`c`。它的运算模式是依次处理每个数组成员一共有四个数组成员所以需要运算4次。
如果采用SIMD模式只要运算一次就够了。
```javascript
var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);
var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12]
```
上面代码之中,数组`a``b`的四个成员的各自相加只用一条指令就完成了。因此速度比上一种写法提高了4倍。
一次SIMD运算可以处理多个数据这些数据被称为“通道”lane。上面代码中一次运算了四个数据因此就是四个通道。
SIMD通常用于矢量运算。
```javascript
v + w = v1, , vn+ w1, , wn
= v1+w1, , vn+wn
```
上面代码中,`v``w`是两个多元矢量。它们的加运算在SIMD下是一个指令、而不是n个指令完成的这就大大提高了效率。这对于3D动画、图像处理、信号处理、数值处理、加密等运算是非常重要的。比如Canvas的`getImageData()`会将图像文件读成一个二进制数组SIMD就很适合对于这种数组的处理。
总得来说SIMD是数据并行处理parallelism的一种手段可以加速一些运算密集型操作的速度。
## 数据类型
SIMD提供多种数据类型。
- Float32x4四个32位浮点数
- Float64x2两个64位浮点数
- Int32x4四个32位整数
- Int16x8八个16位整数
- Int8x16十六个8位整数
- Uint32x4四个无符号的32位整数
- Uint16x8八个无符号的16位整数
- Uint8x16十六个无符号的8位整数
- Bool32x4四个32位布尔值
- Bool16x8八个16位布尔值
- Bool8x16十六个8位布尔值
- Bool64x2两个64位布尔值
每种数据类型被`x`号分隔成两部分,后面的部分表示通道数,前面的部分表示每个通道的宽度和类型。比如,`Float32x4`就表示这个值有4个通道每个通道是一个32位浮点数。
每种数据类型都是一个方法,可以传入参数,生成对应的值。
```javascript
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
```
上面代码中,变量`a`就是一个128位、包含四个32位浮点数即四个通道的值。
注意,这些数据类型方法都不是构造函数,前面不能加`new`,否则会报错。
```javascript
var v = new SIMD.Float32x4(0, 1, 2, 3);
// TypeError: SIMD.Float32x4 is not a constructor
```
如果所有数据通道都是同样的值,可以使用`splat`方法生成值。
```javascript
var v = SIMD.Float32x4.splat(0.0);
```
上面代码中,`v`的四个通道都是`0.0`
如果要取出单个通道的值,可以使用`extractLane`方法。
```javascript
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
var b = SIMD.Float32x4.extractLane(a, 0); // 1.0
```
上面代码中,`extractLane`方法的第一个参数是一个SIMD值第二个参数是通道的编号从0开始
如果要修改某个通道的值,可以使用`replaceLane`方法。
```javascript
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
var c = SIMD.Float32x4.replaceLane(a, 0, 5.0);
```
上面代码中经过替换后得到一个新的SIMD值`(5.0, 2.0, 3.0, 4.0)`。可以看到,`replaceLane`接受三个参数SIMD值、通道的编号从0开始、新的值。
## 方法:数学运算
每种数据类型都有一系列运算符,下面是其中的一些。
- float32x4.abs(v):返回`v`的绝对值
- float32x4.neg(v):返回`v`的绝对值的负值
- float32x4.sqrt(v):返回`v`的平方根
- float32x4.add(v, w)`v``w`对应项的相加
- float32x4.mul(v, w)`v``w`对应项的相乘
- float32x4.equal(v, w):比较`v``w`对应项是否相等,返回的布尔值组成一个`uint32x4`的值
### SIMD.%type%.add()
`add`方法接受两个SIMD值作为参数将它们的每个通道相加返回一个新的SIMD值。
```javascript
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
var b = SIMD.Float32x4(5.0, 10.0, 15.0, 20.0);
var c = SIMD.Float32x4.add(a, b);
```
上面代码中经过加法运算新的SIMD值为`(6.0, 12.0, 18.0. 24.0)`
### SIMD.%type%.mul()
`mul`方法接受两个SIMD值作为参数将它们的每个通道相乘返回一个新的SIMD值。
```javascript
var a = SIMD.Float32x4(-1, -2, 3, 4);
var b = SIMD.Float32x4(3, 3, 3, 3);
SIMD.Float32x4.mul(a, b);
// Float32x4[-3, -6, 9, 12]
```
### SIMD.%type%.shiftLeftByScalar()
`shiftLeftByScalar`方法接受一个SIMD值作为参数然后将每个通道的值左移指定的位数返回一个新的SIMD值。
```javascript
var a = SIMD.Int32x4(1, 2, 4, 8);
SIMD.Int32x4.shiftLeftByScalar(a, 1);
// Int32x4[2, 4, 8, 16]
```
如果左移后,新的值超出了当前数据类型的位数,溢出的部分会被丢弃。
```javascript
var ix4 = SIMD.Int32x4(1, 2, 3, 4);
var jx4 = SIMD.Int32x4.shiftLeftByScalar(ix4, 32);
// Int32x4[0, 0, 0, 0]
```
### SIMD.%type%.shiftRightByScalar()
`shiftRightByScalar`方法接受一个SIMD值作为参数然后将每个通道的值右移指定的位数返回一个新的SIMD值。
```javascript
var a = SIMD.Int32x4(1, 2, 4, -8);
SIMD.Int32x4.shiftRightByScalar(a, 1);
// Int32x4[0, 1, 2, -4]
```
如果原来通道的值是带符号的值,则符号位保持不变,不受右移影响。如果是不带符号位的值,则右移后头部会补`0`
```javascript
var a = SIMD.Uint32x4(1, 2, 4, -8);
SIMD.Uint32x4.shiftRightByScalar(a, 1);
// Uint32x4[0, 1, 2, 2147483644]
```
上面代码中,`-8`右移一位变成了`2147483644`是因为对于32位无符号整数来说`-8`的二进制形式是`11111111111111111111111111111000`,右移一位就变成了`01111111111111111111111111111100`,相当于`2147483644`
## 方法:通道处理
### SIMD.%type%.load()
`load`方法用于从二进制数组读入数据生成一个新的SIMD值。
```javascript
var a = new Int32Array([1,2,3,4,5,6,7,8]);
SIMD.Int32x4.load(a, 0);
// Int32x4[1, 2, 3, 4]
var b = new Int32Array([1,2,3,4,5,6,7,8]);
SIMD.Int32x4.load(a, 2);
// Int32x4[3, 4, 5, 6]
```
`load`方法接受两个参数一个二进制数组和开始读取的位置从0开始。如果位置不合法比如`-1`或者超出二进制数组的大小),就会抛出一个错误。
这个方法还有三个变种`load1()``load2()``load3()`,表示从指定位置开始,只加载一个通道、二个通道、三个通道的值。
```javascript
// 格式
SIMD.Int32x4.load(tarray, index)
SIMD.Int32x4.load1(tarray, index)
SIMD.Int32x4.load2(tarray, index)
SIMD.Int32x4.load3(tarray, index)
// 实例
var a = new Int32Array([1,2,3,4,5,6,7,8]);
SIMD.Int32x4.load1(a, 0);
// Int32x4[1, 0, 0, 0]
SIMD.Int32x4.load2(a, 0);
// Int32x4[1, 2, 0, 0]
SIMD.Int32x4.load3(a, 0);
// Int32x4[1, 2, 3,0]
```
### SIMD.%type%.splat()
`splat`方法返回一个新的SIMD值该值的所有通道都会设成同一个预先给定的值。
```javascript
SIMD.Float32x4.splat(3);
// Float32x4[3, 3, 3, 3]
SIMD.Float64x2.splat(3);
// Float64x2[3, 3]
```
如果省略参数所有整数型的SIMD值都会设定`0`浮点型的SIMD值都会设成`NaN`
### SIMD.%type%.swizzle()
`swizzle`方法返回一个新的SIMD值重新排列原有的SIMD值的通道顺序。
```javascript
var t = SIMD.Float32x4(1, 2, 3, 4);
SIMD.Float32x4.swizzle(t, 1, 2, 0, 3);
// Float32x4[2,3,1,4]
```
上面代码中,`swizzle`方法的第一个参数是原有的SIMD值后面的参数对应将要返回的SIMD值的四个通道。它的意思是新的SIMD的四个通道依次是原来SIMD值的1号通道、2号通道、0号通道、3号通道。由于SIMD值最多可以有16个通道所以`swizzle`方法除了第一个参数以外最多还可以接受16个参数。
下面是另一个例子。
```javascript
var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
// Float32x4[1.0, 2.0, 3.0, 4.0]
var b = SIMD.Float32x4.swizzle(a, 0, 0, 1, 1);
// Float32x4[1.0, 1.0, 2.0, 2.0]
var c = SIMD.Float32x4.swizzle(a, 3, 3, 3, 3);
// Float32x4[4.0, 4.0, 4.0, 4.0]
var d = SIMD.Float32x4.swizzle(a, 3, 2, 1, 0);
// Float32x4[4.0, 3.0, 2.0, 1.0]
```
### SIMD.%type%.shuffle()
`shuffle`方法从两个SIMD值之中取出指定通道返回一个新的SIMD值。
```javascript
var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);
SIMD.Float32x4.shuffle(a, b, 1, 5, 7, 2);
// Float32x4[2, 6, 8, 3]
```
上面代码中,`a``b`一共有8个通道依次编号为0到7。`shuffle`根据编号取出相应的通道返回一个新的SIMD值。
## 方法:比较运算
### SIMD.%type%.greaterThan()
`greatThan`方法用来比较两个SIMD值`a``b`的每一个通道,如果在该通道中,`a`较大就得到`true`,否则得到`false`。最后所有通道的比较结果会组成一个新的SIMD值作为掩码返回。
```javascript
var a = SIMD.Float32x4(1, 6, 3, 11);
var b = SIMD.Float32x4(1, 4, 7, 9);
var mask = SIMD.Float32x4.greaterThan(a,b);
// Bool32x4[false, true, false, true]
```
### SIMD.%type%.lessThan()
`lessThan`方法用来比较两个SIMD值`a``b`的每一个通道,如果在该通道中,`a`较小就得到`true`,否则得到`false`。最后所有通道的比较结果会组成一个新的SIMD值作为掩码返回。
```javascript
var a = SIMD.Float32x4(1, 2, 3, 11);
var b = SIMD.Float32x4(1, 4, 7, 9);
var mask = SIMD.Float32x4.lessThan(a,b);
// Bool32x4[false, true, true, false]
```
### SIMD.%type%.select()
`select`方法通过掩码生成一个新的SIMD值。它接受三个参数分别是掩码和两个SIMD值。
```javascript
var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);
var mask = SIMD.Bool32x4(true, false, false, true);
SIMD.Float32x4.select(mask, a, b);
// Float32x4[1, 6, 7, 4]
```
上面代码中,`select`方法接受掩码和两个SIMD值作为参数。当某个通道对应的掩码为`true`会选择第一个SIMD值的对应通道否则选择第二个SIMD值的对应通道。
这个方法通常与比较运算符结合使用。
```javascript
var a = SIMD.Float32x4(0, 12, 3, 4);
var b = SIMD.Float32x4(0, 6, 7, 50);
var mask = SIMD.Float32x4.lessThan(a,b);
// Bool32x4[false, false, true, true]
var result = SIMD.Float32x4.select(mask, a, b);
// Float32x4[0, 6, 3, 4]
```
上面代码中,先通过`lessThan`方法生成一个掩码,然后通过`select`方法生成一个由每个通道的较小值组成的新的SIMD值。
### SIMD.%type%.allTrue()SIMD.%type%.anyTrue()
`allTrue`方法接受一个SIMD值作为参数然后返回一个布尔值表示该SIMD值的所有通道是否都为`true`
```javascript
var a = SIMD.Bool32x4(true, true, true, true);
var b = SIMD.Bool32x4(true, false, true, true);
SIMD.Bool32x4.allTrue(a); // true
SIMD.Bool32x4.allTrue(b); // false
```
`anyTrue`方法则是只要有一个通道为`true`,就返回`true`,否则返回`false`
```javascript
var a = SIMD.Bool32x4(false, false, false, false);
var b = SIMD.Bool32x4(false, false, true, false);
SIMD.Bool32x4.anyTrue(a); // false
SIMD.Bool32x4.anyTrue(b); // true
```
这两个方法通常与比较运算符结合使用。
```javascript
var ax4 = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);
var bx4 = SIMD.Float32x4(0.0, 6.0, 7.0, 8.0);
var ix4 = SIMD.Float32x4.lessThan(ax4, bx4);
var b1 = SIMD.Int32x4.allTrue(ix4); // false
var b2 = SIMD.Int32x4.anyTrue(ix4); // true
```
### SIMD.%type%.min()SIMD.%type%.minNum()
`min`方法接受两个SIMD值作为参数将它们的每个通道的较小值组成一个新的SIMD值返回。
```javascript
var a = SIMD.Float32x4(-1, -2, 3, 5.2);
var b = SIMD.Float32x4(0, -4, 6, 5.5);
SIMD.Float32x4.min(a, b);
// Float32x4[-1, -4, 3, 5.2]
```
如果有一个通道的值是`NaN`,则会返回`NaN`
```javascript
var c = SIMD.Float64x2(NaN, Infinity)
var d = SIMD.Float64x2(1337, 42);
SIMD.Float64x2.min(c, d);
// Float64x2[NaN, 42]
```
`minNum`方法与`min`方法的作用一模一样,唯一的区别是如果有一个通道的值是`NaN`,则会优先返回另一个通道的值。
```javascript
var ax4 = SIMD.Float32x4(1.0, 2.0, NaN, NaN);
var bx4 = SIMD.Float32x4(2.0, 1.0, 3.0, NaN);
var cx4 = SIMD.Float32x4.min(ax4, bx4);
// Float32x4[1.0, 1.0, NaN, NaN]
var dx4 = SIMD.Float32x4.minNum(ax4, bx4);
// Float32x4[1.0, 1.0, 3.0, NaN]
```
## 实例:求平均值
正常模式下,计算`n`个值的平均值,需要运算`n`次。
```javascript
function average(list) {
var n = list.length;
var sum = 0.0;
for (var i = 0; i < n; i++) {
sum += list[i];
}
return sum / n;
}
```
使用SIMD可以将计算次数减少到`n`次的四分之一。
```javascript
function average(list) {
var n = list.length;
var sum = SIMD.Float32x4.splat(0.0);
for (var i = 0; i < n; i += 4) {
sum = SIMD.Float32x4.add(
sum,
SIMD.Float32x4.load(list, i)
);
}
var total = SIMD.Float32x4.extractLane(sum, 0) +
SIMD.Float32x4.extractLane(sum, 1) +
SIMD.Float32x4.extractLane(sum, 2) +
SIMD.Float32x4.extractLane(sum, 3);
return total / n;
}
```
上面代码先是每隔四位将所有的值读入一个SIMD然后立刻累加。然后得到累加值四个通道的总和再除以`n`就可以了。
## 二进制数组
SIMD可以与二进制数组结合生成数组实例。
```javascript
var _f64x2 = new Float64Array(_f32x4.buffer);
var _i32x4 = new Int32Array(_f32x4.buffer);
var _i16x8 = new Int16Array(_f32x4.buffer);
var _i8x16 = new Int8Array(_f32x4.buffer);
var _ui32x4 = new Uint32Array(_f32x4.buffer);
var _ui16x8 = new Uint16Array(_f32x4.buffer);
var _ui8x16 = new Uint8Array(_f32x4.buffer);
```
下面是一个例子。
```javascript
// a 和 b 是float32x4数组实例
function addArrays(a, b) {
var c = new Float32x4Array(a.length);
for (var i = 0; i < a.length; i++) {
c[i] = SIMD.float32x4.add(a[i], b[i]);
}
return c;
}
```
## 参考链接
- TC39, [SIMD.js Stage 2](https://docs.google.com/presentation/d/1MY9NHrHmL7ma7C8dyNXvmYNNGgVmmxXk8ZIiQtPlfH4/edit#slide=id.p19)
- MDN, [SIMD](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SIMD)
- TC39, [ECMAScript SIMD](https://github.com/tc39/ecmascript_simd)
- Axel Rauschmayer, [JavaScript gains support for SIMD](http://www.2ality.com/2013/12/simd-js.html)