Java可以识别CPU的SIMD优势; 或者只有循环展开的优化效果

hus*_*sik 4 java optimization performance simd loop-unrolling

这部分代码来自我的矢量类的dotproduct方法.该方法对目标矢量数组(1000个矢量)进行内积计算.

当向量长度为​​奇数(262145)时,计算时间为4.37秒.当向量长度(N)为262144(8的倍数)时,计算时间为1.93秒.

     time1=System.nanotime();
     int count=0;
     for(int j=0;j<1000;i++)
     {

             b=vektors[i]; // selects next vector(b) to multiply as inner product.
                           // each vector has an array of float elements.

             if(((N/2)*2)!=N)
             {
                 for(int i=0;i<N;i++)
                 {
                     t1+=elements[i]*b.elements[i];
                 }
             }
             else if(((N/8)*8)==N)
             {
                 float []vek=new float[8];
                 for(int i=0;i<(N/8);i++)
                 {
                     vek[0]=elements[i]*b.elements[i];
                     vek[1]=elements[i+1]*b.elements[i+1];
                     vek[2]=elements[i+2]*b.elements[i+2];
                     vek[3]=elements[i+3]*b.elements[i+3];
                     vek[4]=elements[i+4]*b.elements[i+4];
                     vek[5]=elements[i+5]*b.elements[i+5];
                     vek[6]=elements[i+6]*b.elements[i+6];
                     vek[7]=elements[i+7]*b.elements[i+7];


                     t1+=vek[0]+vek[1]+vek[2]+vek[3]+vek[4]+vek[5]+vek[6]+vek[7];
                     //t1 is total sum of all dot products.
                 }
             }
     }
     time2=System.nanotime();
     time3=(time2-time1)/1000000000.0; //seconds
Run Code Online (Sandbox Code Playgroud)

问题:将时间从4.37s减少到1.93s(快2倍)是JIT明智地决定使用SIMD指令还是只是我的循环展开的正面效果?

如果JIT不能自动进行SIMD优化,那么在这个例子中,JIT也没有自动完成展开优化,这是真的吗?

对于1M迭代(向量)和向量大小为64,加速乘数变为3.5X(缓存优势?).

谢谢.

tmy*_*ebu 8

你的代码有很多问题.你确定你在测量你认为你在测量的东西吗?

你的第一个循环执行此操作,传统缩进:

 for(int j=0;j<1000;i++) {
     b=vektors[i]; // selects next vector(b) to multiply as inner product.
                   // each vector has an array of float elements.
 }
Run Code Online (Sandbox Code Playgroud)

您的滚动循环涉及一长串依赖的加载和存储.您的展开循环涉及8个独立的加载和存储链.如果您使用浮点运算,则JVM无法将其中一个转换为另一个,因为它们的计算基本不同.打破依赖的加载连锁链可以导致现代处理器的大幅加速.

您的滚动循环遍历整个向量.您的展开循环仅迭代第一个(大致)第八个.因此,展开的循环再次计算根本不同的东西.

我还没有看到JVM为你的第二个循环生成矢量化代码,但是我可能已经过了几年就JVM做了什么.-XX:+PrintAssembly在运行代码时尝试使用并检查opto生成的代码.


小智 5

我已经对此进行了一些研究(并且从我在C中使用矩阵乘法的类似项目中获得的知识),但是我的答案很简单,因为我不是这个主题的专家.

至于你的第一个问题,我认为加速来自你的循环展开; 你在for循环方面的条件检查减少了大约87%.据我所知,JVM从1.4开始就支持SSE,但要实际控制你的代码是否使用向量化(并且确切地知道),你需要使用JNI.

在这里查看JNI的示例:是否有任何JVM的JIT编译器生成使用向量化浮点指令的代码?

当您从262144将向量的大小减小到64时,缓存肯定是一个因素.当我在C中完成这个项目时,我们不得不为更大的矩阵实现缓存阻塞,以便利用缓存.您可能想要做的一件事是检查缓存大小.

正如旁注:在flops中测量性能而不是秒,这可能是一个更好的主意,因为程序的运行时间(以秒为单位)可能会因许多不同因素而异,例如当时的CPU使用率.