Vector Instructions
撰写时间:2025-02-09
修订时间:2025-02-16
概述
vector是一种向量操作指令,属于SIMD指令。
SIMD (Single instruction, multiple data) 是一种并行计算技术,允许一条指令同时操作多个数据,从而显著提升数据密集型任务的执行效率。其核心思想是通过单条指令并行处理多个数据,适用于高度规则且数据独立的任务。
SIMD是指令级并行,由硬件直接支持,集成在通用CPU中,具有灵活性及通用性。而多线程(如多核CPU)是线程级并行,依赖于操作系统的调度。两者结合使用,能实现更高呑吐。
SIMD可应用于:
- 图像处理:使用SIMD加速像素操作(如对RGBA通道进行分离、缩放、或应用滤镜)。
- 机器学习:矩阵乘法和激活函数的并行计算。
- 游戏开发:物理引擎中的碰撞检测、粒子系统运算。
- 个人知识库的建立:提取关键字,并进行向量化。
综上,SIMD通过单指令操作多数据,在多媒体处理、科学计算等领域至关重要。结合现代CPU的SIMD扩展指令集,开发者能显著提升程序性能。
因此,vector是Wasm给开发者带来的一份厚礼。
通过调用store及load指令,vector也可用于内存操作。
设置常数
代码:
将4个i32的值分别设置为1, 2, 3, 4。其好处是在128位(16个字节)的范围内,这4个数值自动按i32的位域对齐,也即各个数值的内存区域不会相互覆盖。
指令 | 含义 |
---|---|
v128.const i8x16 (n:i8)16 | 同时生成字长均为1字节的16个整数常数 |
v128.const i16x8 (n:i16)8 | 同时生成字长均为2字节的8个整数常数 |
v128.const i32x4 (n:i32)4 | 同时生成字长均为4字节的4个整数常数 |
v128.const i64x2 (n:i64)2 | 同时生成字长均为8字节的2个整数常数 |
v128.const f32x4 (n:f32)4 | 同时生成字长均为4字节的4个浮点常数 |
v128.const f64x2 (n:f64)2 | 同时生成字长均为8字节的2个浮点常数 |
示例代码如下:
store
store指令用于在内存区域中存储数值。
基本用法
在默认内存区域偏移值为0的地方,存储了指定的4个i32的整数数值。
选取特定通道的值进行存储
可以从要存储的一系列数值中,按不同的位域分成不同的组,再取出特定组别中的数值,再予以存储。
代码从v128.const i8x16
所表示的16个各为1个字节的整数数组中,取出位于第7个通道(lane)的值,保存在偏移值为0的内存区域中。
lane就是在常数数组上所建立起来的通道。对于store8_lane,先按8位即1字节为单位来划分为16个通道,每个通道1字节。
然后,代码:
取出第7个通道的数值,其值为0x07,存储到内存区域中。
划分通道时不受设置常数时所使用的操作数的数据类型的约束。下面代码,按i16来设置一组常数,但按8位来划分通道。
上面两行代码,均使用相同的数据类型为i16、数量为8个的整数常数数组,分别填充了内存区域的第1行及第2行。而在填充第2行时,先按字节划分通道,然后只取出第0x0A个字节处的数值66予以填充。
下面代码从要存储的一系列数值中,分别按8位、16位、32位、64位来划分通道后,均取出第1个通道的值存储进内存中。
因v128的字长为16个字节,因此,通道指令的本质就是从字长为16个字节的一组数据中,划分为不同字长的通道。
下表列出存储数值时可划分通道的所有指令。
指令 | 通道字长 | 通道数量 | 能选取的通道范围 | 选取数值的字长 |
---|---|---|---|---|
v128.store8_lane | 1字节 | 16 | [0, 15] | 1字节 |
v128.store16_lane | 2字节 | 8 | [0, 7] | 2字节 |
v128.store32_lane | 4字节 | 4 | [0, 3] | 4字节 |
v128.store64_lane | 8字节 | 2 | [0, 1] | 8字节 |
load
v128的load指令用于从内存区域中读取数据,并返回一个v128的值。
v128返回值的问题
在Wasm内部,函数可以返回v128的值,但由于JavaScript不直接支持该数据类型,因此,需返回v128的值的函数不能导出至JavaScript中调用。下面的代码将抛出异常。
在Safari中,异常信息为:Exception thrown: an exported wasm function cannot contain a v128 parameter or return value
。
在Chrome中,异常信息为:Exception thrown: type incompatibility when transforming from/to JS
。
此时,可换个思路,不调用v128.load指令,而是改为调用v128.store指令来改写内存区域的内容,再将内存区域导出至JavaScript中操作。
在Wasm内部调用load指令
load指令将指定偏移值的向量读取后,再压进Result Stack中,利用此特性,我们可以在Result Stack中进行相应的运算。
程序一开始,先在内存区域 [0x00, 0x0F] 的位置存储了4个32位的数值。而在doubleUp函数中,代码:
调用v128.load指令,从第0个字节处读取4个32位的数值,压进Result Stack中,然后将4个值均为2的常数压进栈,再将它们按各自的通道分别相乘。
之后,调用v128.store,将结果存储到内存区域偏移值为16的地方。最后,打印内存区域。
由上例可见,v128.load所返回的结果,可用作诸如i32x4
.mul等SIMD指令的参数。
v128.load8x8_sx指令
我们准备选用下面一组数据。
这组数据有以下特点:
- 字长均为2字节
- 高字节的值比低字节的值小1
- 前8位及后8位,其最高权重位均为1,可代表符号位
- 每个字节的十六进制均可独立辨识
- 每个数值均比上个数值大2
下面在Wasm中使用这组数据。
在内存示意图中,第一行是内存原始数据;第二行是调用v128.load8x8_u指令加载相应数据后再存储回内存中的数据;第三行是调用v128.load8x8_s指令加载相应数据后再存储回内存中的数据。
v128.load8x8_sx连续加载位域为8位共计8个的数值,并将每个数值都扩展为16位的数值,最终将这些扩展后的数值组合成一个128位的SIMD向量。需指定高位字节的扩展方式。
v128.load8x8_u指令将每个数值都零扩展(zero-extend)为16位,即在扩展后的16位数值的高字节部位以数值0x00填充。
v128.load8x8_s指令将每个数值都符号扩展(sign-extend)为16位。符号扩展的规则为:
- 若原始数值为正数(最高位为0),则在扩展后的高字节部位填充数值0x00。
- 若原始数值为负数(最高位为1),则在扩展后的高字节部位填充数值0xFF,以保持数值的符号和大小不变。