Package jdk.incubator.vector
孵化功能。将在未来的版本中移除。
此包提供了用于表达矢量计算的类,如果具有适当的硬件和运行时能力,则可以使用矢量硬件指令加速。
矢量是一个具有固定数量的lanes的序列,所有lanes都是某个固定的元素类型,例如byte、long或float。每个lane包含一个独立的元素类型值。矢量上的操作通常是lane-wise的,将某个标量运算符(例如加法)分布到参与矢量的lanes中,通常生成一个矢量结果,其lanes包含各种标量结果。在支持平台上运行时,lane-wise操作可以由硬件并行执行。这种并行方式称为单指令多数据(SIMD)并行。
在SIMD编程风格中,矢量lane内的大多数操作是无条件的,但可以通过使用掩码操作(例如blend())来实现条件执行,控制在相关的VectorMask下。除了严格的lane-wise流动之外的数据移动是通过使用跨lane操作实现的,通常在相关的VectorShuffle的控制下。可以使用各种类型的lane-wise转换和字节级重新格式化重新解释来重新格式化lane数据和/或整个矢量,通常在选择与输入矢量不同的替代矢量格式的反射VectorSpecies对象的控制下。
Vector<E>声明了一组对所有元素类型通用的矢量操作(方法)。这些通用操作包括对lane值的通用访问、数据选择和移动、重新格式化以及所有原始类型通用的某些算术和逻辑操作(例如加法或比较)。
Vector的公共子类型对应于特定的元素类型。这些声明了进一步针对该元素类型特定的操作,包括对整数元素类型的值进行位操作、对浮点元素类型的值进行超越操作等。
一些lane-wise操作,例如add运算符,被定义为一个完整的命名操作,其中Vector上的相应方法具有掩码和未掩码的重载,并且(在子类中)还具有协变重写(返回子类)和额外的标量广播重载(包括掩码和未掩码)。其他lane-wise操作,例如min运算符,被定义为部分服务(不是完全服务)的命名操作,其中Vector和/或子类上的相应方法提供了一些但不是所有可能的重载和重写(通常是未掩码变体与标量广播重载)。最后,所有lane-wise操作(如先前描述的命名操作或其他未命名的方法)都在VectorOperators上声明了相应的operator token作为静态常量。每个操作符令牌定义了一个符号Java表达式,例如a + b用于ADD操作符令牌。通用的lane-wise操作令牌接受方法,例如用于一元lane-wise操作的方法,在Vector上提供,并且具有与完全服务命名操作相同的变体。
此包包含了与每种支持的元素类型对应的Vector的公共子类型:ByteVector、ShortVector、IntVector、LongVector、FloatVector和DoubleVector。
以下是一个示例,演示如何使用矢量计算来将两个float数组a和b的元素相乘,并将结果存储在数组c中。
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
void vectorMultiply(float[] a, float[] b, float[] c) {
// 假设数组参数的大小相同
for (int i = 0; i < a.length; i += SPECIES.length()) {
VectorMask<Float> m = SPECIES.indexInRange(i, a.length);
FloatVector va = FloatVector.fromArray(SPECIES, a, i, m);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i, m);
FloatVector vc = va.mul(vb)
vc.intoArray(c, i, m);
}
}
在上面的示例中,我们使用由indexInRange()生成的掩码来防止读取/写入超出数组长度。前a.length / SPECIES.length()次迭代将具有所有lanes设置的掩码。只有最后一次迭代(如果a.length不是SPECIES.length()的倍数)将具有设置了前a.length % SPECIES.length()个lanes的掩码。由于在所有迭代中都使用了掩码,上述实现可能无法实现最佳性能(对于大数组长度)。可以在不使用掩码的情况下实现相同的计算,如下所示:
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
void vectorMultiply(float[] a, float[] b, float[] c) {
int i = 0;
// 假设数组参数的大小相同
for (; i < SPECIES.loopBound(a.length); i += SPECIES.length()) {
FloatVector va = FloatVector.fromArray(SPECIES, a, i);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
FloatVector vc = va.mul(vb)
vc.intoArray(c, i);
}
for (; i < a.length; i++) {
c[i] = a[i] * b[i];
}
}
在矢量计算之后需要进行标量计算,以处理TLENGTH数组元素的尾部,其中TLENGTH < SPECIES.length()适用于矢量种类。上述示例使用了首选种类(FloatVector.SPECIES_PREFERRED),确保代码动态适应在其运行的平台上的最佳形状。
上述代码中使用了辅助方法loopBound()来找到矢量循环的结束。也可以在这里使用原始掩码表达式,例如(a.length & ~(SPECIES.length() - 1)),因为已知SPECIES.length()为8,这是2的幂。但这并不总是正确的假设。例如,如果FloatVector.SPECIES_PREFERRED的平台相关形状为S_Max_BIT,并且该形状具有一些奇怪的假设大小,例如384(根据某些架构,这是一个有效的矢量大小),那么手动调整的原始掩码表达式可能会产生令人惊讶的结果。
性能注意事项
此包依赖于运行时动态将矢量操作编译为最佳矢量硬件指令的能力。如果无法将操作编译为矢量指令,则会使用每个操作的默认标量实现。用户需要注意以下几点以生成最佳的矢量机器代码:
- 所使用的矢量形状必须受到底层平台的支持。例如,使用
IntVector的VectorShapeS_512_BIT编写的代码将不会在仅支持256位矢量的平台上编译为矢量指令。相反,将使用默认的标量实现。因此,建议使用上面显示的首选种类编写通用大小的矢量计算。 - 此包中定义的大多数类应被视为基于值的类。这个分类适用于
Vector及其子类型、VectorMask、VectorShuffle和VectorSpecies。对于这些类型,==等身份敏感操作可能会产生不可预测的结果,或者性能降低。奇怪的是,v.equals(w)可能比v==w更快,因为equals不是一个身份敏感的方法。此外,这些对象可以存储在局部变量和参数中以及作为static final常量,但将它们存储在其他Java字段或数组元素中,虽然在语义上是有效的,但可能会带来性能风险。
对于此包中的每个类,除非另有说明,否则引用类型的任何方法参数都不得为null,任何null参数都将引发NullPointerException。对于此API的方法,这一事实没有单独记录。
-
ClassDescription代表有序不可变
byte值序列的专用Vector。代表有序不可变double值序列的专用Vector。代表有序不可变序列的专用Vector,其中包含float值。代表有序不可变序列的专用Vector,其中包含int值。代表有序不可变序列的专用Vector,其中包含long值。代表有序不可变序列的专用Vector,其中包含short值。Vector<E>一个固定数量的lanes序列,所有元素类型都是固定的,如byte、long或float。VectorMask<E>VectorMask代表有序不可变序列的boolean值。这个类仅包含描述按lane进行的矢量操作的静态常量,以及对它们进行分类的嵌套接口。所有操作符标记的根类型,提供常见属性的查询,如参数数量、参数和返回类型、符号名称和操作符名称。VectorShape选择特定的Vector实现。VectorShuffle代表有序不可变序列的int值,称为源索引,其中每个源索引从兼容的Vector中选择一个源lane。管理具有相同element type(ETYPE)和shape组合的所有矢量的接口。