使用Java 18的Vector API提高性能 - martin

Java Vector API 为现代 CPU 的数据并行功能提供了一个抽象层。

由于不同的处理器 架构 有不同的风格,因此没有简单的解决方案来利用软件中特定于平台的功能。通常需要以特定于平台的方式编写代码并利用平台的特定功能来获得出色的性能优势。Vector API 试图使开发人员能够以与平台无关的方式编写数据并行软件。

这篇博文试图在一些示例中探索新的 Vector API 提供的可能性,以及对于特定用例是否值得探索潜在的实现。

为了解释 Java Vector API 抽象是如何工作的,我们需要探索不同的 CPU 架构并提供对数据并行计算的基本理解。然而,这个概念并不是那么新。它在 C# 中已经存在了一段时间,并且已被证明是在现代硬件架构上利用数据并行计算的好方法。

与常规计算操作相比,如 1+1,在一次操作中添加两个“数据”,数据并行操作是在多个“数据”上执行简单的操作(例如,+)同时。这种操作模式称为 SIMD(单指令,多数据),而传统的执行方式称为 SISD(单指令,单数据)。性能加速的结果是在一个 CPU 周期内对多个“数据”应用相同的操作。

由于处理器采用不同的 uArch(x86、ARM),它们的 SIMD 实现存在显着差异。

简单求和

从一个简单的例子开始,我们可以详细看看下面的代码片段:

<b>public</b> <b>static</b> <b>int</b>[] simpleSum(<b>int</b>[] a, <b>int</b>[] b) {
    <b>var</b> c = <b>new</b> <b>int</b>[a.length];
    <b>for</b> (<b>var</b> i = 0; i < a.length; i++) {
        c[i] = a[i] + b[i];
    }
    <b>return</b> c;
}

同样的代码通过Vector API翻译成数据并行加速代码:

<b>private</b> <b>static</b> <b>final</b> VectorSpecies<Integer> SPECIES = IntVector.SPECIES_PREFERRED;
<b>public</b> <b>static</b> <b>int</b>[] vectorSum(<b>int</b>[] a, <b>int</b>[] b) {
    <b>var</b> c = <b>new</b> <b>int</b>[a.length];
    <b>var</b> upperBound = SPECIES.loopBound(a.length);

    <b>var</b> i = 0;
    <b>for</b> (; i < upperBound; i += SPECIES.length()) {
        <b>var</b> va = IntVector.fromArray(SPECIES, a, i);
        <b>var</b> vb = IntVector.fromArray(SPECIES, b, i);
        <b>var</b> vc = va.add(vb);
        vc.intoArray(c, i);
    }
    <font><i>// Compute elements not fitting in the vector alignment.</i></font><font>
    <b>for</b> (; i < a.length; i++) {
        c[i] = a[i] + b[i];
    }

    <b>return</b> c;

}
</font>

这段代码需要更多的说明。

在代码能够运行并利用SIMD加速之前,必须确定数据宽度。AVX兼容CPU可以处理256比特,而AVX-512可以提供512比特的数据宽度。

private static final VectorSpecies<Integer> SPECIES = IntVector.SPECIES_PREFERRED;

因此,我们需要正确配置循环的大小。简单的for-loop使用i++将索引递增1;在这个例子中,需要根据SIMD寄存器的数据宽度进行转移。

在AVX-512(512位)上运行的整数运算的情况下,我们必须以16为单位递增。

第一个迭代执行a[0]+b[0]到a[15]+b[15]的操作。

下一个操作对a[16]+b[16]到a[31]+b[31]执行同样的操作,以此类推

<font><i>//Determines the last index that fits the registers and cuts off any 'overhanging' items.</i></font><font>
<b>var</b> upperBound = SPECIES.loopBound(a.length);

<b>var</b> i = 0;
</font><font><i>// Increment by the data-width!</i></font><font>
<b>for</b> (; i < upperBound; i += SPECIES.length()) {
   ....
}
</font>

最后,需要处理所有剩余的项目,这些项目没有在数据宽度内对齐。因此,该操作必须以非并行的方式进行,从第一个未被矢量循环触及的项目开始。

<b>for</b> (; i < a.length; i++) { <font><i>// <a href="https://www.jdon.com/tags/40250" target="_blank"><b>clean</b></a>up loop</i></font><font>
    c[i] = a[i] + b[i];
}
</font>

Vector API的代码可能看起来有点奇怪,但与我以前使用C 的经验相比 ,设计非常相似。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值