这种悲伤指令的奇怪行为的原因是什么?
What is the reason for this weird behavior of sad instruction?
我已经使用 SSE2 实现了 program 来分别比较 AVX2 和 SSE2 的 vpsadbw
指令和 psadbw
。以下代码为SSE2程序:
#define MAX1 4096
#define MAX2 MAX1
#define MAX3 MAX1
#define NUM_LOOP 1000000000
double pTime = 0, mTime = 5;
//global data for sequentila matrix operations
unsigned char a_char[MAX1][MAX2] __attribute__(( aligned(16)));
unsigned char b_char[MAX2][MAX3] __attribute__(( aligned(16)));
unsigned char c_char[MAX1][MAX3] __attribute__(( aligned(16)));
unsigned short int temp[8];
int main()
{
int i, j, w=0, sad=0;
struct timespec tStart, tEnd;
double tTotal , tBest=10000;
__m128i vec1, vec2, vecT, sad_total;
sad_total= _mm_setzero_si128();
do{
clock_gettime(CLOCK_MONOTONIC,&tStart);
for(i=0; i<MAX1; i++){
for(j=0; j<MAX2; j+=16){
vec1 = _mm_load_si128((__m128i *)&a_char[i][j]);
vec2 = _mm_load_si128((__m128i *)&b_char[i][j]);
vecT = _mm_sad_epu8( vec1 , vec2);
sad_total = _mm_add_epi64(vecT, sad_total);
}
}
_mm_store_si128((__m128i *)&temp[0], sad_total);
sad=temp[0]+temp[2]+temp[4]+temp[6];
clock_gettime(CLOCK_MONOTONIC,&tEnd);
tTotal = (tEnd.tv_sec - tStart.tv_sec);
tTotal += (tEnd.tv_nsec - tStart.tv_nsec) / 1000000000.0;
if(tTotal<tBest)
tBest=tTotal;
pTime += tTotal;
} while(w++ < NUM_LOOP && pTime < mTime);
printf(" The best time: %lf sec in %d repetition for %dX result is %d matrix\n",tBest,w, MAX1, sad);
return 0;
}
我用gcc
、skylake
、Linux mint
当我生成汇编代码时,内部循环包含一些不需要的移动操作,如下 SSE2:
.L26:
vmovdqa xmm1, XMMWORD PTR a_char[rcx+rax]
vpsadbw xmm1, xmm1, XMMWORD PTR b_char[rcx+rax]
add rax, 16
vpaddq xmm3, xmm1, XMMWORD PTR [rsp]
cmp rax, 4096
vmovaps XMMWORD PTR [rsp], xmm3
jne .L26
由于 AVX2 生成此汇编代码:
.L26:
vmovdqa ymm1, YMMWORD PTR a_char[rcx+rax]
vpsadbw ymm1, ymm1, YMMWORD PTR b_char[rcx+rax]
add rax, 32
vpaddq ymm2, ymm2, ymm1
cmp rax, 4096
jne .L26
我不知道那些严重违反性能的 2 move 指令的原因。
您直接绕过编译器并告诉它通过 _mm_load_si128 使用 movdqa。它正在做你告诉它做的事情。这里有什么问题?我还注意到你沿着 16 字节边界对齐,如果我错了请随时纠正我(我不确定 attribute 在你的编译器上是如何实现的)但你可能获取填充作为结果,以便每个元素将在 16 字节边界上对齐;如果是这样,这将影响您展开的影响。如果没有,请随时纠正我。
原因是这样的:
_mm_store_si128((__m128i *)&temp[0], sad_total);
Clang 不介意并且无论如何都会编写出很好的代码,但 GCC 不喜欢它(也许失败的启发式方法?)
将其替换为不会触发 "this should be on the stack all the time"-heuristic 的东西后,GCC 会生成更好的代码,例如:(未测试)
__m128i sad_total = _mm_setzero_si128();
for(i = 0; i < MAX1; i++) {
for(j = 0; j < MAX2; j += 16) {
__m128i vec1 = _mm_load_si128((__m128i *)&a_char[i][j]);
__m128i vec2 = _mm_load_si128((__m128i *)&b_char[i][j]);
__m128i vecT = _mm_sad_epu8( vec1 , vec2);
sad_total = _mm_add_epi64(sad_total, vecT);
}
}
__m128i hsum = _mm_add_epi64(sad_total, _mm_bsrli_si128(sad_total, 8));
sad = _mm_cvtsi128_si32(hsum);
内部循环现在看起来像
.L2:
vmovdqa xmm1, XMMWORD PTR a_char[rdx+rax]
vpsadbw xmm1, xmm1, XMMWORD PTR b_char[rdx+rax]
add rax, 16
vpaddq xmm2, xmm1, xmm2
cmp rax, 4096
jne .L2
我已经使用 SSE2 实现了 program 来分别比较 AVX2 和 SSE2 的 vpsadbw
指令和 psadbw
。以下代码为SSE2程序:
#define MAX1 4096
#define MAX2 MAX1
#define MAX3 MAX1
#define NUM_LOOP 1000000000
double pTime = 0, mTime = 5;
//global data for sequentila matrix operations
unsigned char a_char[MAX1][MAX2] __attribute__(( aligned(16)));
unsigned char b_char[MAX2][MAX3] __attribute__(( aligned(16)));
unsigned char c_char[MAX1][MAX3] __attribute__(( aligned(16)));
unsigned short int temp[8];
int main()
{
int i, j, w=0, sad=0;
struct timespec tStart, tEnd;
double tTotal , tBest=10000;
__m128i vec1, vec2, vecT, sad_total;
sad_total= _mm_setzero_si128();
do{
clock_gettime(CLOCK_MONOTONIC,&tStart);
for(i=0; i<MAX1; i++){
for(j=0; j<MAX2; j+=16){
vec1 = _mm_load_si128((__m128i *)&a_char[i][j]);
vec2 = _mm_load_si128((__m128i *)&b_char[i][j]);
vecT = _mm_sad_epu8( vec1 , vec2);
sad_total = _mm_add_epi64(vecT, sad_total);
}
}
_mm_store_si128((__m128i *)&temp[0], sad_total);
sad=temp[0]+temp[2]+temp[4]+temp[6];
clock_gettime(CLOCK_MONOTONIC,&tEnd);
tTotal = (tEnd.tv_sec - tStart.tv_sec);
tTotal += (tEnd.tv_nsec - tStart.tv_nsec) / 1000000000.0;
if(tTotal<tBest)
tBest=tTotal;
pTime += tTotal;
} while(w++ < NUM_LOOP && pTime < mTime);
printf(" The best time: %lf sec in %d repetition for %dX result is %d matrix\n",tBest,w, MAX1, sad);
return 0;
}
我用gcc
、skylake
、Linux mint
当我生成汇编代码时,内部循环包含一些不需要的移动操作,如下 SSE2:
.L26:
vmovdqa xmm1, XMMWORD PTR a_char[rcx+rax]
vpsadbw xmm1, xmm1, XMMWORD PTR b_char[rcx+rax]
add rax, 16
vpaddq xmm3, xmm1, XMMWORD PTR [rsp]
cmp rax, 4096
vmovaps XMMWORD PTR [rsp], xmm3
jne .L26
由于 AVX2 生成此汇编代码:
.L26:
vmovdqa ymm1, YMMWORD PTR a_char[rcx+rax]
vpsadbw ymm1, ymm1, YMMWORD PTR b_char[rcx+rax]
add rax, 32
vpaddq ymm2, ymm2, ymm1
cmp rax, 4096
jne .L26
我不知道那些严重违反性能的 2 move 指令的原因。
您直接绕过编译器并告诉它通过 _mm_load_si128 使用 movdqa。它正在做你告诉它做的事情。这里有什么问题?我还注意到你沿着 16 字节边界对齐,如果我错了请随时纠正我(我不确定 attribute 在你的编译器上是如何实现的)但你可能获取填充作为结果,以便每个元素将在 16 字节边界上对齐;如果是这样,这将影响您展开的影响。如果没有,请随时纠正我。
原因是这样的:
_mm_store_si128((__m128i *)&temp[0], sad_total);
Clang 不介意并且无论如何都会编写出很好的代码,但 GCC 不喜欢它(也许失败的启发式方法?)
将其替换为不会触发 "this should be on the stack all the time"-heuristic 的东西后,GCC 会生成更好的代码,例如:(未测试)
__m128i sad_total = _mm_setzero_si128();
for(i = 0; i < MAX1; i++) {
for(j = 0; j < MAX2; j += 16) {
__m128i vec1 = _mm_load_si128((__m128i *)&a_char[i][j]);
__m128i vec2 = _mm_load_si128((__m128i *)&b_char[i][j]);
__m128i vecT = _mm_sad_epu8( vec1 , vec2);
sad_total = _mm_add_epi64(sad_total, vecT);
}
}
__m128i hsum = _mm_add_epi64(sad_total, _mm_bsrli_si128(sad_total, 8));
sad = _mm_cvtsi128_si32(hsum);
内部循环现在看起来像
.L2:
vmovdqa xmm1, XMMWORD PTR a_char[rdx+rax]
vpsadbw xmm1, xmm1, XMMWORD PTR b_char[rdx+rax]
add rax, 16
vpaddq xmm2, xmm1, xmm2
cmp rax, 4096
jne .L2