java 向量计算 jvm向量化 您所在的位置:网站首页 向量化执行 java 向量计算 jvm向量化

java 向量计算 jvm向量化

2023-07-15 03:23| 来源: 网络整理| 查看: 265

摘要

        这篇文章简单解释了向量化的目的,它是如何在Java中生效的,如何去检测它是否被应用于Java程序中。这些知识对数学计算大有帮助。

        这些方式比较底层,且只适用于特殊的场景。如果你想优化你的Java程序性能。你的首选应是其他可用的优化方法。只有当您已经使用其他技术优化了Java代码,并在之后对其进行了分析,并且得出结论,专注于算术计算的部分可能会通过并行化运行得更快,那么本文可能对您有用。

1.简介1.1 SIMD

        通常程序代码是被串行执行的。计算型程序通常需要在数据上做大量计算。一般情况下多数数据需要被相同的方式处理。例如,对于1000个粒子的模拟,模拟中会有一个步骤,用其当前速度更新每个粒子的位置 v: 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];

        也就是说在多个数据上一次性执行一个操作。 在汇编代码级别,有专门针对这些分组操作的说明。此概念被叫做single instruction, multiple data简称SIMD。

        对于+操作来说SIMD指令的被称作addps(SSE instruction set) 或者vaddps(AVX instruction set) 在X86 架构上。它使用两组数据作为操作数。每组4 (SSE instruction set)或8个 (AVX instruction set)数据。 对分组中的每个数据进行加法操作。对于上面的例子来说 s[0..3]是一组v[0..3]是另一组,x86的汇编代码如下

addps %xmm0,%xmm1 ;add vector in xmm0 to vector in xmm1, store result in xmm01.2 向量化

        SIMD是从指令设计者(即CPU制造商)的角度给出的概念名称。但这不是唯一的观点。在数学上,由固定数量的元素组成的有序组(s[0..3] and v[0..3]) 被称作向量。所以SIMD也被叫做向量指令。这只是对同一件事的另一个观点,是从使用指令的用户观点来看的。

        矢量化是使用矢量指令来加速程序执行。矢量化可以由程序员完成,也可以由编译器自动实现。在后一种情况下,它被称为自动矢量化(auto vectorization)。

        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 Java 中的向量指令

        编写Java程序后,Java文件中的Java源代码被编译成字节码并保存到类文件中。然后,在程序执行之前或执行过程中,它的字节码通常会再次编译,这次是从字节码到本机机器码。后一种编译通常在程序执行时进行,因此它是JIT编译( JIT compilation)。   

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

 2. 可以从矢量化受益的代码            

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

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

        如上,语句a[i] = a[i] * a[i] 在数组的多个连续元素上执行。编译器可以对此进行检查,而不是单独执行每个*操作,它可以使用向量指令一次计算多个结果。

3. 检查向量化是否被使用3.1 准备一个Mirco基准

        为了生成汇编级别的向量指令,我们首先需要创建一个可以从向量指令中获益且可以运行的java程序。 如下:

/** * Run with this command to show native assembly: * 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); } } }

        上面的程序可以使编译器把square进行优化。

3.2 运行Mirco基准

        

Open Eclipse and create a new project. Create a new class in the new project and name it VectorizationMicroBenchmark. Copy-paste the code of Snippet 2 into it.Right-click the file and from the dropdown menu, choose Run > Java Application (ignore the output for now)In Eclipse's menu, click Run > Run Configurations...A window opened. In the window, find VectorizationMicroBenchmark, click it and choose the Arguments tabin the Arguments tab, under VM arguments: put in this:-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,VectorizationMicroBenchmark.square get libhsdis and copy (possibly rename) the file hsdis-amd64.so (.dll for windows) to your Java-home/lib directory. On Ubuntu, this is something like/usr/lib/jvm/Java-11-openjdk-amd64/lib.run VectorizationMicroBenchmark again

        如果你没有Eclipse, 你可以在任何其他IDE中类似地执行这些步骤,或者在命令行上使用文本编辑器和javac/java。

        第7步,将大量信息打印到控制台,其中一部分是反汇编的本机代码。如果您看到很多消息,但没有诸如mov、push、add等汇编指令,那么您可能可以在输出中的某个地方找到以下消息:Could not load hsdis-amd64.so; library not loadable; PrintAssembly is disabled

        如果您看到此消息,这意味着Java找不到文件hsdis-amd64.so-它不在正确的目录中或没有正确的名称。在Linux上,创建符号链接时也会发生这种情况。Java在这里不接受符号链接。您必须复制该文件。hsdis-amd64.so是显示生成的本机代码所需的反汇编程序。JIT编译器将Java字节码编译为本机机器码后,hsdis-amd64.so用于反汇编本机机器码,使其具有可读性。您可以找到有关如何获取/安装它以及如何在JVM中查看JIT编译代码的更多信息(How to see JIT-compiled code in JVM.)。

3.3  输出说明

        在输出中找到汇编指令后,您可能会惊讶地发现,您不仅找到了square(...)方法的汇编代码,而且你发现了它的多个版本。这是因为JIT编译器在第一次运行时没有完全优化该方法。在调用该方法之后,它将其编译为本机代码而不进行优化。在多次调用之后,它通过一些优化(但不是全部优化)再次编译该方法。只有在几千次调用之后,编译器才确信该方法非常重要,需要在编译时启用所有优化,包括向量化。因此,最好的编译通常是输出中的最后一个编译。

        开始搜索输出末尾的“第11行”,向后搜索。您可能会发现如下内容:

0x...ac70: vmovss 0x10(%rbx,%rbp,4),%xmm0 ;*faload {reexecute=0 rethrow=0 return_oop=0} ; - VectorizationMicroBenchmark::square@9 (line 11) 0x...ac76: vmulss %xmm0,%xmm0,%xmm1 0x...ac7a: vmovss %xmm1,0x10(%rbx,%rbp,4) ;*fastore {reexecute=0 rethrow=0 return_oop=0} ; - VectorizationMicroBenchmark::square@14 (line 11)

请注意代码段3中末尾带有-ss的指令vmulss。

vmulss: multiply scalar single-precision floating-point values

 vmulss仅将一个浮点数与另一个浮点数相乘。所以这不是我们想要的。(这里,标量表示只有一个,单精度表示32位,即浮点而不是双精度)。相反,我们希望找到一条指令,它一次将许多浮点与许多其他浮点相乘。所以请继续关注。你最终会发现:

0x...ac54: vmovdqu 0x10(%rbx,%rbp,4),%ymm0 ;*faload {reexecute=0 rethrow=0 return_oop=0} ; - VectorizationMicroBenchmark::square@9 (line 11) 0x...ac5a: vmulps %ymm0,%ymm0,%ymm0 0x...ac5e: vmovdqu %ymm0,0x10(%rbx,%rbp,4) ;*fastore {reexecute=0 rethrow=0 return_oop=0} ; - VectorizationMicroBenchmark::square@14 (line 11)

 vmulps: multiply packed single-precision floating-point values

 vmulps是一条真正的SIMD指令(提示:SIMD=单指令,多数据=矢量化指令)。这里,(packed )“打包”是指在一个寄存器中打包在一起的多个元素。这表明应用了自动矢量化。

4  引用

计划对Java进行扩展,这将允许程序员在源代码中显式使用向量指令。在编写本文时,这些扩展还没有准备好(Spring2020)。See JEP-338 and Project Panama: Interconnecting JVM and native code for details and status. (back)

5 参考文档 

SSE - illustration of the results of -ps and -ss instructions:

x86 and amd64 instruction reference - x86 and amd64 instruction reference

http://jpbempel.blogspot.com/2015/12/printassembly-output-explained.html - PrintAssembly output explained

SIMD原理 – 孙希栋的博客

java 向量计算 jvm向量化_Java

https://www.sunxidong.com/357.html

c++ SIMD 样例

SIMD指令初学



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有