Sto calcolando la media e la varianza di un array utilizzando gli intrinseci SSE. Fondamentalmente, questa è la somma dei valori e le sue piazze che può essere illustrato dal seguente programma:Risultati errati durante l'uso di instrinsics SSE in Visual Studio 2010/2012 e nella modalità di rilascio
int main(int argc, const char* argv[])
{
union u
{
__m128 m;
float f[4];
} x;
// Allocate memory and initialize data: [1,2,3,...stSize+1]
const size_t stSize = 1024;
float *pData = (float*) _aligned_malloc(stSize*sizeof(float), 32);
for (size_t s = 0; s < stSize; ++s) {
pData[s] = s+1;
}
// Sum and sum of squares
{
// Accumlation using SSE intrinsics
__m128 mEX = _mm_set_ps1(0.f);
__m128 mEXX = _mm_set_ps1(0.f);
for (size_t s = 0; s < stSize; s+=4)
{
__m128 m = _mm_load_ps(pData + s);
mEX = _mm_add_ps(mEX, m);
mEXX = _mm_add_ps(mEXX, _mm_mul_ps(m,m));
}
// Final reduction
x.m = mEX;
double dEX = x.f[0] + x.f[1] + x.f[2] + x.f[3];
x.m = mEXX;
double dEXX = x.f[0] + x.f[1] + x.f[2] + x.f[3];
std::cout << "Sum expected: " << (stSize * stSize + stSize)/2 << std::endl;
std::cout << "EX: " << dEX << std::endl;
std::cout << "Sum of squares expected: " << 1.0/6.0 * stSize * (stSize + 1) * (2 * stSize + 1) << std::endl;
std::cout << "EXX: " << dEXX << std::endl;
}
// Clean up
_aligned_free(pData);
}
Ora, quando compila e si esegue il programma in modalità debug ottengo il seguente (e corretta) output:
Sum expected: 524800
EX: 524800
Sum of squares expected: 3.58438e+008
EXX: 3.58438e+008
Tuttavia, compilare ed eseguire il programma in modalità di rilascio dei seguenti (e sbagliato) i risultati vengono prodotti:
Sum expected: 524800
EX: 524800
Sum of squares expected: 3.58438e+008
EXX: 3.49272e+012
Modifica dell'ordine di accumulazione, vale a dire . EXX sia aggiornato prima EX, i risultati sono OK:
Sum expected: 524800
EX: 524800
Sum of squares expected: 3.58438e+008
EXX: 3.58438e+008
Sembra un'ottimizzazione del compilatore 'controproducente' o perché è l'ordine di esecuzione in questione? È un bug noto?
MODIFICA: Ho appena visto l'output dell'assembler. Ecco cosa ricevo (solo le parti rilevanti). Per la build di rilascio con /arch:AVX
flag di compilazione abbiamo:
; 69 : // Second test: sum and sum of squares
; 70 : {
; 71 : __m128 mEX = _mm_set_ps1(0.f);
vmovaps xmm1, XMMWORD PTR [email protected]
mov ecx, 256 ; 00000100H
; 72 : __m128 mEXX = _mm_set_ps1(0.f);
vmovaps xmm2, xmm1
npad 12
[email protected]:
; 73 : for (size_t s = 0; s < stSize; s+=4)
; 74 : {
; 75 : __m128 m = _mm_load_ps(pData + s);
vmovaps xmm0, xmm1
; 76 : mEX = _mm_add_ps(mEX, m);
vaddps xmm1, xmm1, XMMWORD PTR [rax]
add rax, 16
; 77 : mEXX = _mm_add_ps(mEXX, _mm_mul_ps(m,m));
vmulps xmm0, xmm0, xmm0
vaddps xmm2, xmm0, xmm2
dec rcx
jne SHORT [email protected]
Questo è chiaramente sbagliato come questa (1) salva il risultato accumulato EX (xmm1
) in xmm0
(2) accumula EX con il valore corrente (XMMWORD PTR [rax]
) e (3) si accumula in EXX (xmm2
) il quadrato del risultato EX accumulato precedentemente salvato in xmm0
.
Al contrario, la versione senza il /arch:AVX
guarda bene e come previsto:
; 69 : // Second test: sum and sum of squares
; 70 : {
; 71 : __m128 mEX = _mm_set_ps1(0.f);
movaps xmm1, XMMWORD PTR [email protected]
mov ecx, 256 ; 00000100H
; 72 : __m128 mEXX = _mm_set_ps1(0.f);
movaps xmm2, xmm1
npad 10
[email protected]:
; 73 : for (size_t s = 0; s < stSize; s+=4)
; 74 : {
; 75 : __m128 m = _mm_load_ps(pData + s);
movaps xmm0, XMMWORD PTR [rax]
add rax, 16
dec rcx
; 76 : mEX = _mm_add_ps(mEX, m);
addps xmm1, xmm0
; 77 : mEXX = _mm_add_ps(mEXX, _mm_mul_ps(m,m));
mulps xmm0, xmm0
addps xmm2, xmm0
jne SHORT [email protected]
Questo sembra davvero come un insetto. Qualcuno può confermare o confutare questo problema con una versione del compilatore diversa? (Attualmente non ho il permesso di aggiornare il compilatore)
Sembra un bug per me. Non riesco a riprodurre il problema sulla versione '16.00.40219.01 per 80x86' (MSVC2010), sia in console che in devenv. Potresti provare ad installare alcuni service pack, forse è stato risolto in qualche momento. Inoltre, potrei suggerire di usare la seconda unione 'y' invece di' x' per ridurre 'EXX'. – stgatilov
Prova ad aggiornare il compilatore. MSVC ha una storia di errori nell'integrazione di SSE/AVX. – Mysticial
Hai compilato la modalità a 64 bit? Visual Studio (la versione che ho usato) di default in modalità a 32 bit. –