4

这部分代码来自我的一个向量类的点积方法。该方法对目标向量数组(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

问题:将时间从 4.37 秒减少到 1.93 秒(快 2 倍)是 JIT 使用 SIMD 指令的明智决定还是我的循环展开的积极影响?

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

对于 1M 次迭代(向量)和 64 的向量大小,加速乘数达到 3.5 倍(缓存优势?)。

谢谢。

4

2 回答 2

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.
 }

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

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

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

于 2013-07-03T23:08:36.407 回答
5

我对此进行了一些研究(并且从我在 C 中使用矩阵乘法所做的类似项目中汲取了知识),但是我的回答持保留态度,因为我绝不是该主题的专家。

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

在此处查看 JNI 的示例:Do any JVM's JIT compilers generate code that using vectorized floating point instructions?

当您将向量的大小从 262144 减少到 64 时,缓存绝对是一个因素。当我在 C 中做这个项目时,为了利用缓存,我们必须为更大的矩阵实现缓存阻塞。您可能想要做的一件事是检查您的缓存大小。

顺便说一句:以 flops 而不是秒来衡量性能可能是一个更好的主意,因为程序的运行时间(以秒为单位)可能会因许多不同的因素而异,例如当时的 CPU 使用率。

于 2013-07-03T23:01:50.497 回答