我有一些循环中的代码

for(int i = 0; i < n; i++)
{
  u[i] = c * u[i] + s * b[i];
}

因此,u和b是相同长度的向量,c和s是标量。该代码是用于与SSE一起使用以获得加速的矢量化的良好候选者吗?

更新

我学会了矢量化(事实证明,如果您使用内在的情况并不难),并在SSE中实现了我的循环。但是,当在VC ++编译器中设置SSE2标志时,我的性能与自己的SSE代码相同。另一方面,英特尔编译器比我的SSE代码或VC ++编译器要快得多。

这是我编写的代码供参考

double *u = (double*) _aligned_malloc(n * sizeof(double), 16);
for(int i = 0; i < n; i++)
{
   u[i] = 0;
}

int j = 0;
__m128d *uSSE = (__m128d*) u;
__m128d cStore = _mm_set1_pd(c);
__m128d sStore = _mm_set1_pd(s);
for (j = 0; j <= i - 2; j+=2)
{
  __m128d uStore = _mm_set_pd(u[j+1], u[j]);

  __m128d cu = _mm_mul_pd(cStore, uStore);
  __m128d so = _mm_mul_pd(sStore, omegaStore);

  uSSE[j/2] = _mm_add_pd(cu, so);
}
for(; j <= i; ++j)
{
  u[j] = c * u[j] + s * omegaCache[j];
}
有帮助吗?

解决方案

是的,这是矢量化的绝佳候选者。但是,在您这样做之前, 确保您已经介绍了您的代码 为了确保这实际上值得优化。也就是说,矢量化将是这样的:

int i;
for(i = 0; i < n - 3; i += 4)
{
  load elements u[i,i+1,i+2,i+3]
  load elements b[i,i+1,i+2,i+3]
  vector multiply u * c
  vector multiply s * b
  add partial results
  store back to u[i,i+1,i+2,i+3]
}

// Finish up the uneven edge cases (or skip if you know n is a multiple of 4)
for( ; i < n; i++)
  u[i] = c * u[i] + s * b[i];

为了获得更多性能,您可以考虑预取进一步的数组元素,和/或展开循环并使用 软件管道 要将计算与一个循环中的计算交织在一起,可以从不同的迭代中访问内存。

其他提示

可能是的,但是您必须为编译器提供一些提示。__restrict__ 放在指针上告诉编译器,两个指针之间没有别名。如果您知道向量的对齐方式,请将其传达给编译器(视觉C ++可能有一些设施)。

我本人不熟悉视觉C ++,但我听说这对矢量化不利。考虑使用英特尔编译器。英特尔允许对生成的组件进行非常细粒度的控制: http://www.intel.com/software/products/compilers/docs/clin/main_cls/cref_cls/common/common/cppref_pragma_vector.htm

_mm_set_pd 不被矢量化。如果从字面上看,它将使用标量操作读取两个双打,然后将两个标量双打组合并将其复制到SSE寄存器中。采用 _mm_load_pd 反而。

是的,这是Vectorizaton的绝佳候选人,假设U和B数组没有重叠。但是该代码由内存访问(LOAD/Store)绑定。矢量化有助于减少每个循环的循环,但是由于U和B阵列上的缓存缺失,指令将停滞不前。 Intel C/C ++编译器将生成以下代码,并使用Xeon X5500处理器的默认标志。编译器将循环通过8展开,并使用XMM [0-16] SIMD寄存器使用SIMD ADD(ADDPD)和乘数(MULPD)指令。在每个周期中,假设您已经准备好寄存器中的数据,则处理器可以发出2个SIMD指令,产生4条标量ILP。

在这里,u,b,c和s是双重精度(8个字节)。

    ..B1.14:                        # Preds ..B1.12 ..B1.10
    movaps    %xmm1, %xmm3                                  #5.1
    unpcklpd  %xmm3, %xmm3                                  #5.1
    movaps    %xmm0, %xmm2                                  #6.12
    unpcklpd  %xmm2, %xmm2                                  #6.12
      # LOE rax rcx rbx rbp rsi rdi r8 r12 r13 r14 r15 xmm0 xmm1 xmm2 xmm3
    ..B1.15:     # Preds ..B1.15 ..B1.14
    movsd     (%rsi,%rcx,8), %xmm4                          #6.21
    movhpd    8(%rsi,%rcx,8), %xmm4                         #6.21
    mulpd     %xmm2, %xmm4                                  #6.21
    movaps    (%rdi,%rcx,8), %xmm5                          #6.12
    mulpd     %xmm3, %xmm5                                  #6.12
    addpd     %xmm4, %xmm5                                  #6.21
    movaps    16(%rdi,%rcx,8), %xmm7                        #6.12
    movaps    32(%rdi,%rcx,8), %xmm9                        #6.12
    movaps    48(%rdi,%rcx,8), %xmm11                       #6.12
    movaps    %xmm5, (%rdi,%rcx,8)                          #6.3
    mulpd     %xmm3, %xmm7                                  #6.12
    mulpd     %xmm3, %xmm9                                  #6.12
    mulpd     %xmm3, %xmm11                                 #6.12
    movsd     16(%rsi,%rcx,8), %xmm6                        #6.21
    movhpd    24(%rsi,%rcx,8), %xmm6                        #6.21
    mulpd     %xmm2, %xmm6                                  #6.21
    addpd     %xmm6, %xmm7                                  #6.21
    movaps    %xmm7, 16(%rdi,%rcx,8)                        #6.3
    movsd     32(%rsi,%rcx,8), %xmm8                        #6.21
    movhpd    40(%rsi,%rcx,8), %xmm8                        #6.21
    mulpd     %xmm2, %xmm8                                  #6.21
    addpd     %xmm8, %xmm9                                  #6.21
    movaps    %xmm9, 32(%rdi,%rcx,8)                        #6.3
    movsd     48(%rsi,%rcx,8), %xmm10                       #6.21
    movhpd    56(%rsi,%rcx,8), %xmm10                       #6.21
    mulpd     %xmm2, %xmm10                                 #6.21
    addpd     %xmm10, %xmm11                                #6.21
    movaps    %xmm11, 48(%rdi,%rcx,8)                       #6.3
    addq      $8, %rcx                                      #5.1
    cmpq      %r8, %rcx                                     #5.1
    jl        ..B1.15       # Prob 99%                      #5.1

这取决于您如何将U和B放置在内存中。如果两个内存块彼此远远遥不可及,那么在这种情况下,SSE不会提高太大。

建议数组u和b是AOE(结构的数组),而不是SOA(数组的结构),因为您可以将它们都加载到单个指令中。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top