Tôi đã vector hóa sản phẩm chấm giữa 2 vectơ với SSE 4.2 và AVX 2, như bạn có thể xem bên dưới. Mã được biên dịch bằng GCC 4.8.4 với cờ tối ưu hóa -O2. Theo dự kiến, hiệu suất tốt hơn với cả hai (và AVX 2 nhanh hơn SSE 4.2), nhưng khi tôi lập mã với PAPI, tôi phát hiện ra rằng tổng số lần bị lỗi (chủ yếu là L1 và L2) tăng lên rất nhiều:Số lượng bộ nhớ cache bị giảm khi mã vectơ
Nếu không có bản vẽ Gia:
PAPI_L1_TCM: 784,112,091
PAPI_L2_TCM: 195,315,365
PAPI_L3_TCM: 79,362
Với SSE 4.2:
PAPI_L1_TCM: 1,024,234,171
PAPI_L2_TCM: 311,541,918
PAPI_L3_TCM: 68,842
Với AVX 2:
PAPI_L1_TCM: 2,719,959,741
PAPI_L2_TCM: 1,459,375,105
PAPI_L3_TCM: 108,140
Có thể có điều gì đó sai với mã của tôi hay là loại hành vi này bình thường không?
AVX 2 mã:
double vec_dotProduct(const vec& vecs, const unsigned int& start_a, const unsigned int& start_b, const int& n) {
double dot = 0;
register int i = 0;
const int loopBound = n-3;
__m256d vsum, vecPi, vecCi, vecQCi;
vsum = _mm256_set1_pd(0);
double * const pA = vecs.x+start_a ;
double * const pB = vecs.x+start_b ;
for(; i<loopBound ;i+=4){
vecPi = _mm256_loadu_pd(&(pA)[i]);
vecCi = _mm256_loadu_pd(&(pB)[i]);
vecQCi = _mm256_mul_pd(vecPi,vecCi);
vsum = _mm256_add_pd(vsum,vecQCi);
}
vsum = _mm256_hadd_pd(vsum, vsum);
dot = ((double*)&vsum)[0] + ((double*)&vsum)[2];
for(; i<n; i++)
dot += pA[i] * pB[i];
return dot;
}
SSE 4.2 đang: Mã
double vec_dotProduct(const vec& vecs, const unsigned int& start_a, const unsigned int& start_b, const int& n) {
double dot = 0;
register int i = 0;
const int loopBound = n-1;
__m128d vsum, vecPi, vecCi, vecQCi;
vsum = _mm_set1_pd(0);
double * const pA = vecs.x+start_a ;
double * const pB = vecs.x+start_b ;
for(; i<loopBound ;i+=2){
vecPi = _mm_load_pd(&(pA)[i]);
vecCi = _mm_load_pd(&(pB)[i]);
vecQCi = _mm_mul_pd(vecPi,vecCi);
vsum = _mm_add_pd(vsum,vecQCi);
}
vsum = _mm_hadd_pd(vsum, vsum);
_mm_storeh_pd(&dot, vsum);
for(; i<n; i++)
dot += pA[i] * pB[i];
return dot;
}
Non-vectorized:
double dotProduct(const vec& vecs, const unsigned int& start_a, const unsigned int& start_b, const int& n) {
double dot = 0;
register int i = 0;
for (i = 0; i < n; ++i)
{
dot += vecs.x[start_a+i] * vecs.x[start_b+i];
}
return dot;
}
Edit: hội của mã không vectorized:
0x000000000040f9e0 <+0>: mov (%rcx),%r8d
0x000000000040f9e3 <+3>: test %r8d,%r8d
0x000000000040f9e6 <+6>: jle 0x40fa1d <dotProduct(vec const&, unsigned int const&, unsigned int const&, int const&)+61>
0x000000000040f9e8 <+8>: mov (%rsi),%eax
0x000000000040f9ea <+10>: mov (%rdi),%rcx
0x000000000040f9ed <+13>: mov (%rdx),%edi
0x000000000040f9ef <+15>: vxorpd %xmm0,%xmm0,%xmm0
0x000000000040f9f3 <+19>: add %eax,%r8d
0x000000000040f9f6 <+22>: sub %eax,%edi
0x000000000040f9f8 <+24>: nopl 0x0(%rax,%rax,1)
0x000000000040fa00 <+32>: mov %eax,%esi
0x000000000040fa02 <+34>: lea (%rdi,%rax,1),%edx
0x000000000040fa05 <+37>: add $0x1,%eax
0x000000000040fa08 <+40>: vmovsd (%rcx,%rsi,8),%xmm1
0x000000000040fa0d <+45>: cmp %r8d,%eax
0x000000000040fa10 <+48>: vmulsd (%rcx,%rdx,8),%xmm1,%xmm1
0x000000000040fa15 <+53>: vaddsd %xmm1,%xmm0,%xmm0
0x000000000040fa19 <+57>: jne 0x40fa00 <dotProduct(vec const&, unsigned int const&, unsigned int const&, int const&)+32>
0x000000000040fa1b <+59>: repz retq
0x000000000040fa1d <+61>: vxorpd %xmm0,%xmm0,%xmm0
0x000000000040fa21 <+65>: retq
Edit2: Dưới đây bạn có thể tìm thấy sự so sánh của cache L1 giữa vectorized và mã không vectorized cho N lớn hơn (N trên nhãn x và L1 cache trên nhãn y). Về cơ bản, đối với các N lớn hơn thì vẫn còn nhiều lỗi trong phiên bản vectơ hơn là trong phiên bản không được vectơ hóa.
Bạn đã xem bản lắp ráp mà trình biên dịch của bạn đã tạo ra (trình biên dịch nào bạn đang sử dụng, bằng cách này?) Có lẽ trình biên dịch cũng đã vector hóa mã của bạn, nhưng đã làm tốt hơn? – Rostislav
@Rostislav Tôi đang sử dụng GNU GCC 4.8.4. Tôi quên đề cập đến nhưng hiệu suất thực sự tốt hơn, mặc dù số lượng các lần bỏ lỡ cao hơn (tôi sẽ thêm số này vào bài đăng đầu tiên). – fc67
Chúng tôi thực sự cần xem mã được tạo cho trường hợp đầu tiên (không được vector). –