【发布时间】:2020-04-30 16:20:47
【问题描述】:
我正在尝试找到一个简洁的示例,它在 x86-64 系统上的 java 中显示 auto vectorization。
我在 for 循环中使用 y[i] = y[i] + x[i] 实现了以下代码。这段代码可以从自动矢量化中受益,所以我认为 java 应该在运行时使用 SSE 或 AVX 指令对其进行编译以加快速度。
但是,我在生成的本机机器代码中找不到矢量化指令。
VecOpMicroBenchmark.java 应该受益于自动矢量化:
/**
* Run with this command to show native assembly:<br/>
* java -XX:+UnlockDiagnosticVMOptions
* -XX:CompileCommand=print,VecOpMicroBenchmark.profile VecOpMicroBenchmark
*/
public class VecOpMicroBenchmark {
private static final int LENGTH = 1024;
private static long profile(float[] x, float[] y) {
long t = System.nanoTime();
for (int i = 0; i < LENGTH; i++) {
y[i] = y[i] + x[i]; // line 14
}
t = System.nanoTime() - t;
return t;
}
public static void main(String[] args) throws Exception {
float[] x = new float[LENGTH];
float[] y = new float[LENGTH];
// to let the JIT compiler do its work, repeatedly invoke
// the method under test and then do a little nap
long minDuration = Long.MAX_VALUE;
for (int i = 0; i < 1000; i++) {
long duration = profile(x, y);
minDuration = Math.min(minDuration, duration);
}
Thread.sleep(10);
System.out.println("\n\nduration: " + minDuration + "ns");
}
}
为了确定它是否被矢量化,我做了以下操作:
- 打开eclipse并创建上述文件
- 右键单击文件并从下拉菜单中选择运行 > Java 应用程序(暂时忽略输出)
- 在eclipse菜单中,点击Run > Run Configurations...
- 在打开的窗口中,找到 VecOpMicroBenchmark,点击它并选择参数选项卡
- 在参数选项卡中,在 VM 参数下: 输入:
-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,VecOpMicroBenchmark.profile -
获取 libhsdis 并将文件
hsdis-amd64.so(Windows 为 .dll)复制(可能重命名)到 java/lib 目录。就我而言,这是/usr/lib/jvm/java-11-openjdk-amd64/lib。 - 再次运行 VecOpMicroBenchmark
它现在应该将大量信息打印到控制台,其中一部分是由 JIT 编译器生成的反汇编本机代码。如果您看到很多消息,但没有像 mov、push、add 等汇编指令,那么也许您可以在某处找到以下消息:
Could not load hsdis-amd64.so; library not loadable; PrintAssembly is disabled
这意味着 java 找不到文件 hsdis-amd64.so - 它不在正确的目录中或没有正确的名称。
hsdis-amd64.so 是显示生成的本机机器代码所需的反汇编程序。在 JIT 编译器将 java 字节码编译为本机机器码后,hsdis-amd64.so 用于反汇编本机机器码以使其可读。您可以在How to see JIT-compiled code in JVM? 找到有关如何获取/安装它的更多信息。
在输出中找到汇编指令后,我浏览了一下(太多了,无法在此处发布所有内容)并寻找line 14。我发现了这个:
0x00007fac90ee9859: nopl 0x0(%rax)
0x00007fac90ee9860: cmp 0xc(%rdx),%esi ; implicit exception: dispatches to 0x00007fac90ee997f
0x00007fac90ee9863: jnb 0x7fac90ee9989
0x00007fac90ee9869: movsxd %esi,%rbx
0x00007fac90ee986c: vmovss 0x10(%rdx,%rbx,4),%xmm0 ;*faload {reexecute=0 rethrow=0 return_oop=0}
; - VecOpMicroBenchmark::profile@16 (line 14)
0x00007fac90ee9872: cmp 0xc(%rdi),%esi ; implicit exception: dispatches to 0x00007fac90ee9997
0x00007fac90ee9875: jnb 0x7fac90ee99a1
0x00007fac90ee987b: movsxd %esi,%rbx
0x00007fac90ee987e: vmovss 0x10(%rdi,%rbx,4),%xmm1 ;*faload {reexecute=0 rethrow=0 return_oop=0}
; - VecOpMicroBenchmark::profile@20 (line 14)
0x00007fac90ee9884: vaddss %xmm1,%xmm0,%xmm0
0x00007fac90ee9888: movsxd %esi,%rbx
0x00007fac90ee988b: vmovss %xmm0,0x10(%rdx,%rbx,4) ;*fastore {reexecute=0 rethrow=0 return_oop=0}
; - VecOpMicroBenchmark::profile@22 (line 14)
所以它使用了 AVX 指令vaddss。但是,如果我在这里是正确的,vaddss 表示
添加标量单精度浮点值,这只会将一个浮点值添加到另一个浮点值(这里,标量意味着只有一个,而这里single 表示 32 位,即 float 而不是 double)。
我在这里期望的是vaddps,这意味着添加压缩单精度浮点值,这是一条真正的SIMD 指令(SIMD = 单指令,多数据= 矢量化指令)。这里,packed 表示多个浮点数打包在一个寄存器中。
关于..ss 和..ps,见http://www.songho.ca/misc/sse/sse.html:
SSE 定义了两种类型的操作;标量和打包。标量运算仅对最不重要的数据元素(位 0~31)进行运算,打包运算并行计算所有四个元素。 SSE 指令具有后缀 -ss 用于标量操作(单标量)和 -ps 用于打包操作(并行标量)。
问题:
我的 java 示例是否不正确,或者为什么输出中没有 SIMD 指令?
【问题讨论】:
-
这里只是猜测,但您可能需要确保编译器知道两个数组的大小均为
LENGTH。显然,每个元素访问都会检查索引是否在相应数组的范围内,如果不在则抛出异常。这很可能会禁用矢量化。 -
自动向量化是昂贵的(就编译时间而言)找到可能的情况,并且安全和正确。 JVM 仅进行 JIT 编译,而提前编译器有大量时间来搜索优化。当 JIT 编译器无法向量化,或者当他们制作笨拙的坏标量汇编(例如Why is 2 * (i * i) faster than 2 * i * i in Java?)时,不要感到惊讶,甚至无法使用明显的窥视孔优化,如
lea用于移位并添加到新目标。
标签: java assembly vectorization x86-64 simd