Java 自动向量化

1. 简介

1.1 SIMD

通常,程序的代码是串行执行的。这意味着单个命令或语句按顺序执行,一个接一个。以算术为重点的程序是对数字进行大量计算的程序。通常,这些程序处理大量数据,并且许多信息以相同的方式一个接一个地处理。例如。对于 1000 个粒子的模拟,模拟中将有一个步骤,用其当前速度 v 更新每个粒子的位置 s:s = s + v。这必须对每个粒子完成,即对粒子数组 s [0] 到 s[999]。
如果您可以将几个粒子组合成一个批次并一次性处理该批次,则可以加快速度。对于上面的示例和一组 4 个粒子,这意味着将计算分组如下:

s[0] = s[0] + v[0];
s[1] = s[1] + v[1];
s[2] = s[2] + v[2];
s[3] = s[3] + v[3];

更一般地说,这一次对多个数据元素执行一种操作。在汇编代码级别,有专门针对这些分组操作的指令。因此这个概念称为单指令多数据,简称SIMD。

在 x86 CPU 上,+ 的 SIMD 指令称为 addps(SSE 指令集)或 vaddps(AVX 指令集)。它需要两个组作为操作数,其中每个组有 4 个元素 (SSE) 或 8 个元素 (AVX)。它将一组的每个元素添加到另一组的相应元素。在上面的例子中,s[0…3] 是一个组,v[0…3] 是另一个组。生成的 x86 汇编代码是:

addps  %xmm0,%xmm1   ;add vector in xmm0 to vector in xmm1, store result in xmm0

1.2. Vectorization

SIMD 是从指令设计者(即 CPU 制造商)的角度给出的概念名称。但这并不是唯一的观点。在数学中,具有固定数量元素(s[0…3] 和 v[0…3])的有序群被称为向量。因此 SIMD 指令也称为向量指令。这只是同一件事的另一个角度,这次是从用户的说明。

向量化是使用向量指令来加速程序执行。向量化可以由程序员完成,或者向量化的可能性可以由编译器自动实现。在后一种情况下,它称为自动向量化。

Auto vectorization is a kind of code optimization which is done by a compiler, either by an AOT compiler at compile time, or by a JIT compiler at execution time.

1.3. Vector Instructions in Java

编写 Java 程序后,Java 文件中的 Java 源代码被编译为字节码并保存到类文件中。然后,在程序执行之前或执行期间,通常会再次编译其字节码,这次是从字节码转换为本地机器码。后一种编译通常在程序执行时完成,因此它是 JIT 编译。

在 Java 中,目前向量化不是由程序员完成的,而是由编译器自动完成的。编译器接受标准的 Java 字节码并自动确定哪一部分可以转换为向量指令。 OpenJDK 或 Oracle 的 Java 等常见 Java 环境可以生成向量化机器代码。

2. Code That Can Benefit From Vectorization

如果代码对数组的许多连续元素执行相同的操作,则可以将其转换为向量化指令。例子:

float[] a = ...

for (int i = 0; i < a.length; i++) {
    a[i] = a[i] * a[i];
}

3. 检查是否使用到向量化

3.1. Prepare a Mirco Benchmark

想要看生成的向量指令汇编代码,我们首先必须创建一个可以从向量指令中受益的可编译和可运行的 Java 程序。为此,将上述 for 循环放入 Java 文件中,放入 square(…) 方法以及 main(…) 方法。编写代码,使 square(…) 执行一百万次,或至少几十万次。这使编译器相信 square(…) 是一种值得最优化的方法。然后称 square(…) 为“热运行”或包含“热循环”。这种热运行是通过 main(…) 中的 for 循环实现的。所以我们有两个循环,一个在 main(…) 中,一个在 square(…) 中。热循环是 square(…)中的循环。

/**
 * Run with this command to show native assembly:<br/>
 * Java -XX:+UnlockDiagnosticVMOptions
 * -XX:CompileCommand=print,VectorizationMicroBenchmark.square
 * VectorizationMicroBenchmark
 */
public class VectorizationMicroBenchmark {

    private static void square(float[] a) {
        for (int i = 0; i < a.length; i++) {
            a[i] = a[i] * a[i]; // line 11
        }
    }

    public static void main(String[] args) throws Exception {
        float[] a = new float[1024];

        // repeatedly invoke the method under test. this
        // causes the JIT compiler to optimize the method
        for (int i = 0; i < 1000 * 1000; i++) {
            square(a);
        }
    }
}

3.2 linux 命令行运行输出汇编指令

前提条件:需要将 hsdis-amd64.so 放到 /usr/lib/jvm/java-11/lib/server 目录

3.2.1 安装 hsdis-amd64.so
git clone https://github.com/liuzhengyang/hsdis
cd hsdis
tar -zxvf binutils-2.26.tar.gz
make BINUTILS=binutils-2.26 ARCH=amd64
cp build/linux-amd64/hsdis-amd64.so /usr/lib/jvm/java-11/lib/server
3.2.2 运行输出汇编
java -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,VecOpMicroBenchmark.profile  -XX:+PrintAssembly VectorizationMicroBenchmark.java > VectorizationMicroBenchmark.s

查看 VectorizationMicroBenchmark.s 中的汇编指令,找到 square 函数对应的指令,可以看到使用了 intel 向量化指令 vmulps 。
在这里插入图片描述

/* * 基于数组的向量实现 */ package dsa; public class Vector_Array implements Vector { private final int N = 1024;//数组的容量 private int n = 0;//向量的实际规模 private Object[] A;//对象数组 //构造函数 public Vector_Array() { A = new Object[N]; n = 0; } //返回向量中元素数目 public int getSize() { return n; } //判断向量是否为空 public boolean isEmpty() { return (0 == n) ? true : false; } //取秩为r的元素 public Object getAtRank(int r)//O(1) throws ExceptionBoundaryViolation { if (0 > r || r >= n) throw new ExceptionBoundaryViolation("意外:秩越界"); return A[r]; } //将秩为r的元素替换为obj public Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation { if (0 > r || r >= n) throw new ExceptionBoundaryViolation("意外:秩越界"); Object bak = A[r]; A[r] = obj; return bak; } //插入obj,作为秩为r的元素;返回该元素 public Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation { if (0 > r || r > n) throw new ExceptionBoundaryViolation("意外:秩越界"); if (n >= N) throw new ExceptionBoundaryViolation("意外:数组溢出"); for (int i=n; i>r; i--) A[i] = A[i-1];//后续元素顺次后移 A[r] = obj;//插入 n++;//更新当前规模 return obj; } //删除秩为r的元素 public Object removeAtRank(int r) throws ExceptionBoundaryViolation { if (0 > r || r >= n) throw new ExceptionBoundaryViolation("意外:秩越界"); Object bak = A[r]; for (int i=r; i<n; i++) A[i] = A[i+1];//后续元素顺次前移 n--;//更新当前规模 return bak; } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值