更多博客请见 我的语雀知识库

我在参与腾讯犀牛鸟开源时,认领了有关Vector API的issue。但找遍全网也找不到关于Vector API具体使用的介绍。于是我简单学习Vector API之后写下这篇简单的介绍供大家参考。个人水平有限。不足之处请大家多多指正。

【JEP咖啡屋18[熟肉]-如何通过Vector API加速并行计算】https://www.bilibili.com/video/BV1Nh4y1s7dJ?vd_source=6df57b2b8470f0f2c9187d097a219270

介绍

Vector API是自JDK16开始孵化的预览功能,至今仍在孵化中(2024.08.02)。
它适用于多个数组的混合运算。将数组转化为多组定长的向量进行计算以加快计算速度。而不适用于单数组的内部运算。如:w[i] = w[i - 16] ^ w[i - 9]

使用

在使用Vector API中有一个非常重要的概念需要知晓:

Species

Species是用于确定你的机器能够将几个数组元素转化为向量的关键变量。一般它的长度在64位到512位之间。即可以将2到16个int数组元素转化为一组向量。或者可以将4到32个shortInt数组元素转化为一组向量。
Species.length()与机器有关。但我们也可以手动指定。
定义一个64位的VectorSpecies物种如下:
private static final VectorSpecies<Integer> SPECIES = IntVector.SPECIES_64;

从数组中创建向量

IntVector中提供了一个创建向量的方法fromArray
方法签名如下:
IntVector fromArray(VectorSpecies<Integer> species,int[] a, int offset)

  • 创建向量必须根据Species创建
  • 从数组a中创建向量
  • offset表示从数组第几位开始创建向量

    向量写回数组

    IntVector中提供了一个写回数组的方法intoArray
    方法签名:void intoArray(int[] a, int offset)
    offset表示从数组第几位开始写入。

mask

在标准情况下,并不是每个数组的所有元素都能转化为向量的
前文提到了:Vector API将数组转化为多组定长的向量。完全转化需要数组长度能被Species.length整除
但不是每个数组长度都能被Species.length整除

比如数组array = [0, 1, 2, 3, 4………35],Species.length = 16。只有前32个元素能够被转化为向量, 剩下的子数组[32, 33, 34, 35]则不能够被转化
fromArray方法内部对offset有检查。如果offset >= array.length - Species.length,那么会抛出indexOutOfLength异常。

因此,对于剩下的元素,一般推荐使用原始方式计算。
formArray方法也有一个重载版本可以将剩余元素也转化为向量。
重载版本的签名如下
IntVector fromArray(VectorSpecies<Integer> species,int[] a, int offset,VectorMask<Integer> m)
多传入一个mask变量,即可做到即使offset >= array.length - Species.length,也能转化为向量。但需要offset < array.length
mask变量使用示例如下:

/*
    SPECIES.length() = 16
    Array.length = 35
*/
for (int i = 0; i < Array.length; i += SPECIES.length()) {
  var mask = SPECIES.indexInRange(i, w.length);
  IntVector v = IntVector.fromArray(SPECIES, Array, i - 16, mask);
}

在代码var mask = SPECIES.indexInRange(i, array.length);中,定义变量的var关键字自JDK10开始引入。它允许你在声明变量时省略其类型,编译器会自动推断出其类型。类似于lambda表达式的无类型参数。

lanwise方法

lanewise直接翻译就是车道交叉。在Vector API中,向量被视为车道,不同向量之间的运算被视为车道交叉。
简单的加减乘除,在jdk.incubator.vector.Vector类中已经封装了。
例如:

var va = IntVector.fromArray(SPECIES, a, i);
var vb = IntVector.fromArray(SPECIES, b, i);
var vc = va.add(vb);
vc.intoArray(c, i);

reducesLanes方法

这是一种归约操作,将向量中各个元素通过某种操作组合起来。如计算一组元素的乘积,总和,最大值,最小值等等

以下为计算一组元素的平方和的代码

IntVector v = IntVector.fromArray(IVectorSpecies.SPECIES_128, new int[]{1, 2, 3, 4});
var v2 = v.mult(v);
int sum = v2.reduceLanes(VectorOperators.ADD);

更加复杂的操作

我们应该如何对向量进行复杂的操作呢?
jdk.incubator.vector.VectorOperators中已经定义了很多常用的操作,与或非、异或、同或等等
在向量A的lanewise方法中,传入运算符的定义与另一个向量B。即可让A与B执行特定的运算。如
IntVector temp = wPrev16.lanewise(VectorOperators.XOR, wPrev9)
向量temp是 wPrev16和wPrev9的异或运算结果。

如何编译运行使用了Vector API的代码?

由于Vector API至今仍处于孵化期,直接使用必然报错。需要在编译时指定两个参数--enable-preview--add-modules=jdk.incubator.vector
在哪里指定?
网络上一些博客说的在pom文件中添加编译参数:

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgs>
                        <arg>--enable-preview</arg>
                        <arg>--add-modules=jdk.incubator.vector</arg>
                    </compilerArgs>
                    <compilerVersion>21</compilerVersion>
                    <source>21</source>
                    <target>21</target>
                </configuration>
            </plugin>
        </plugins>

    </build>

对我而言并没有用。
在IDEA的运行配置中添加也没有用:
image.png

于是我只能使用最原始的办法。在命令行中运行

image.pngimg.png

我的机器上的JDK版本是21。想使用Vector API最低也要JDK16。