我将以下循环向量化,在我正在开发的应用程序中出现:
void vecScl(Node** A, Node* B, long val){
int fact = round( dot / const);
for(i=0; i<SIZE ;i++)
(*A)->vector[i] -= fact * B->vector[i];
}
这是SSE代码:
void vecSclSSE(Node** A, Node* B, long val){
int fact = round( dot / const);
__m128i vecPi, vecQi, vecCi, vecQCi, vecResi;
int sseBound = SIZE/4;
for(i=0,j=0; j<sseBound ; i+=4,j++){
vecPi = _mm_loadu_si128((__m128i *)&((*A)->vector)[i] );
vecQi = _mm_set_epi32(fact,fact,fact,fact);
vecCi = _mm_loadu_si128((__m128i *)&((B)->vector)[i] );
vecQCi = _mm_mullo_epi32(vecQi,vecCi);
vecResi = _mm_sub_epi32(vecPi,vecQCi);
_mm_storeu_si128((__m128i *) (((*A)->vector) + i), vecResi );
}
//Compute remaining positions if SIZE % 4 != 0
for(; i<SIZE ;i++)
(*A)->vector[i] -= q * B->vector[i];
}
虽然这在正确性方面起作用,但无论是否有SSE,性能都完全相同。我用以下代码编译代码:
g++ *.cpp *.h -msse4.1 -march=corei7-avx -mtune=corei7-avx -mno-avx -mno-aes -Warray-bounds -O2
这是因为我没有分配(并使用相应的SSE功能)对齐的内存吗?代码变化非常复杂,所以我现在有点想避免这样做。
顺便说一句,就进一步改进而言,考虑到我受到Sandy Bridge架构的限制,我能做的最好的是什么?编辑:编译器尚未对代码进行矢量化。首先,我将向量的数据类型更改为short
s,这不会改变性能。然后,我使用-fno-tree-vectorize
进行编译,性能相同。
非常感谢
答案 0 :(得分:3)
如果您的数据很大,那么您可能只是受内存限制,因为您每个加载/存储只执行很少的ALU操作。
但是,您可以尝试一些小的改进:
inline void vecSclSSE(Node** A, Node* B, long val){
// make function inline, for cases where `val` is small
const int fact = (dot + const / 2 - 1) / const;
// use integer arithmetic here if possible
const __m128i vecQi = _mm_set1_epi32(fact);
// hoist constant initialisation out of loop
int32_t * const pA = (*A)->vector; // hoist invariant de-references out of loop
int32_t * const pB = B->vector;
__m128i vecPi, vecCi, vecQCi, vecResi;
for(int i = 0; i < SIZE - 3; i += 4) { // use one loop variable
vecPi = _mm_loadu_si128((__m128i *)&(pA[i]));
vecCi = _mm_loadu_si128((__m128i *)&(pB[i]));
vecQCi = _mm_mullo_epi32(vecQi,vecCi);
vecResi = _mm_sub_epi32(vecPi,vecQCi);
_mm_storeu_si128((__m128i *)&(pA[i]), vecResi);
}
//Compute remaining positions if SIZE % 4 != 0
for(; i<SIZE ;i++)
pA[i] -= q * pB[i];
}
答案 1 :(得分:1)
正如保罗所说,每次数据访问的计算相对较少,而且您的代码可能是IO绑定的。由于未对齐的存储/加载比对齐的存储/加载慢,因此您应该对齐数据。
您应该将16个字节与SSE对齐,SSE也是一个缓存行,并且(我认为)32与AVX对齐。如果您自己分配了数据,只需使用_aligned_alloc
即可。如果您使用std::vector
,最简单的对齐方式是使用自定义分配器而不是std::allocator
。此分配器会调用_aligned_alloc
或类似的内容,而不是malloc
/ new
。另请参阅this question。
然后你可以切换到加载/存储的对齐指令。
另外,我不确定&((*A)->vector)[i]
生成的代码是什么,最好使用本地指针来存储数据,但请务必对其进行注释__restrict
但在开始这一切之前,请确保它值得您的时间和维护负担。您可以使用oprofile
for linux或AMD's code analyst
for windows。
答案 2 :(得分:-1)
我想说,对于相同的SIZE ,我能够对第一篇文章中的内核进行矢量化。这一次,我有很大的加速(我不会说这个因素,因为它是无关紧要的,除非我量化内核在整个应用程序中花费的时间)。内核计算两个向量的点积,即:
for(i=0;i<SIZE;i++)
dot += A->vector[i] * B->vector[i];
由此可以得出结论,SIZE小是不成问题的。反过来,这表明我可能在第一个内核中做错了。有人可以为第一个内核建议一组不同的SSE操作吗?我认为值得尝试一下。下一步是分配对齐的内存,但如前所述,这在Sandy Bridge和其他新架构中并不重要。
再次证明了编译器没有对代码进行矢量化。
由于