我在循环中有一些代码
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];
}
答案 0 :(得分: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在一个循环中将计算与来自不同迭代的内存访问交错。
答案 1 :(得分:1)
可能是的,但你必须帮助编译器提供一些提示。
放在指针上的__restrict__
告诉编译器两个指针之间没有别名。
如果您知道向量的对齐方式,请将其传达给编译器(Visual C ++可能有一些功能)。
我自己并不熟悉Visual C ++,但我听说它对矢量化没有好处。 请考虑使用英特尔编译器。 英特尔允许对生成的汇编进行非常精细的控制:http://www.intel.com/software/products/compilers/docs/clin/main_cls/cref_cls/common/cppref_pragma_vector.htm
答案 2 :(得分:1)
_mm_set_pd
没有矢量化。如果从字面上理解,它使用标量操作读取两个双精度,然后组合两个标量双精度并将它们复制到SSE寄存器中。请改用_mm_load_pd
。
答案 3 :(得分:1)
是的,假设U和B数组没有重叠,这是vectorizaton的理想选择。但代码受内存访问(加载/存储)的约束。矢量化有助于减少每个循环的周期,但由于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
答案 4 :(得分:-1)
这取决于你如何将u和b放在记忆中。 如果两个内存块彼此相距很远,那么SSE在这种情况下不会提高太多。
建议数组u和b是AOE(结构数组)而不是SOA(数组结构),因为你可以在单个指令中将它们加载到寄存器中。