2010-05-27 197 views
6

我有一些代码在一个循环SSE SIMD优化For循环

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

所以,u和b是相同的长度的矢量,并且c和s是标量。这个代码是否适合与SSE一起使用以加速矢量化?

UPDATE

我学到矢量(原来,这并不难,如果您使用内联函数),并实现我的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]; 
} 
+0

[注VC11现在在它的优化使用SIMD(http://blogs.microsoft.co.il/blogs/sasha/archive/2011/10/17/simd-optimized-c-code-in -visual-studio-11.aspx) – bobobobo 2012-10-13 17:30:00

回答

5

是的,这是一个优秀的矢量化候选人。但是,在你这样做之前,确保你已经对你的代码进行了简介,以确保这实际上是值得优化的。这就是说,矢量化会去是这样的:

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]; 

为了获得更大的性能,可以考虑预取进一步的数组元素,和/或展开循环使用software pipelining与内存交错在一个循环计算从不同的迭代访问。

+0

明确发现此代码是一个瓶颈。检查我学习和实现向量化的问题不是浪费的努力 - 编译器通常不会自动将这些代码自动向量化? – 2010-05-27 04:17:23

+1

@Projectile如果你告诉编译器关于混叠,通常它会。根据我自己的经验,生成比编译器更好的代码是非常不寻常的,不需要付出非常大的努力。 – Anycorn 2010-05-27 04:26:48

-1

代码这取决于你如何放置u和b在内存中。 如果两个内存块彼此距离很远,在这种情况下SSE不会提升太多。

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

+1

我不同意在这里使用AOS比SOA更有优势。你仍然为每家商店做2次装载,而使用AOS,你现在必须在每4个装置中只写回2次。使用SOA,您可以从'u'加载4个单元,从'b'加载4个单元,然后将4写回到'u',而无需执行任何混洗或屏蔽。 – 2010-05-27 04:00:03

+0

两点都不同意YeenFei。对于垂直SIMD,SOA通常更胜一筹。由于缓存,内存距离并不是一个相关因素 - 即使AOS允许使用较少的缓存行(例如,由于极端的对齐填充,这本质上不太可能发生在SIMD化的良好候选中),但重构这些仍然会更好缓存线是SOA。 – mabraham 2012-11-14 12:26:31

1

可能是的,但你必须帮助编译器提供一些提示。放在指针上的 __restrict__告诉编译器两个指针之间没有别名。 如果你知道你的向量对齐,将其传递给编译器(Visual C++可能有一些设施)。

我自己对Visual C++并不熟悉,但我听说它对矢量化没有好处。 请考虑使用英特尔编译器。 Intel允许对生成的程序集进行相当细致的控制:http://www.intel.com/software/products/compilers/docs/clin/main_cls/cref_cls/common/cppref_pragma_vector.htm

+1

谁比他们更了解英特尔处理器? :) – YeenFei 2010-05-27 05:05:03

1

_mm_set_pd未被矢量化。如果从字面上看,它使用标量操作读取两个双精度值,然后结合两个标量双精度值并将它们复制到SSE寄存器中。改为使用_mm_load_pd

1

是的,这是一个很棒的候选vectorizaton,假设没有U和B数组的重叠。但是代码受内存访问(加载/存储)的约束。向量化有助于减少每个循环的周期,但由于U和B阵列上的缓存未命中,指令会停止。英特尔C/C++编译器使用Xeon x5500处理器的默认标志生成以下代码。编译器将循环展开8,并使用xmm [0-16] SIMD寄存器采用SIMD ADD(addpd)和MULTIPLY(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