SSE組み込み関数を使用して配列の平均と分散を計算しています。基本的に、これは次のプログラムに示すことができる値とその乗の合計である:私は、デバッグモードでプログラムをコンパイルして実行するとVisual Studio 2010/2012およびリリースモードでSSEイントリンジンを使用した場合の結果が正しくない
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);
}
は、今私は、次の(そして正しい)取得出力:
しかしSum expected: 524800
EX: 524800
Sum of squares expected: 3.58438e+008
EXX: 3.58438e+008
、以下の(間違った)結果が生成されているリリースモードでプログラムをコンパイルし、実行している:
Sum expected: 524800
EX: 524800
Sum of squares expected: 3.58438e+008
EXX: 3.49272e+012
蓄積の順序の変更、すなわち。EXXはEX前に更新され、結果はOKです:
Sum expected: 524800
EX: 524800
Sum of squares expected: 3.58438e+008
EXX: 3.58438e+008
は「逆」コンパイラの最適化のように見えるか、なぜ実行順序が関連していますか?これは既知のバグですか?
編集: 私はアセンブラの出力を見ました。ここに私が得るものがあります(関連する部分のみ)。我々が持っている/arch:AVX
コンパイラフラグとリリースビルドの場合 :
; 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]
これは、このように明らかに間違っている(1)xmm0
に蓄積されたEX結果(xmm1
)を保存(2)現在の値を持つEX(XMMWORD PTR [rax]
)を蓄積(3)EXX(xmm2
)に蓄積されたEX結果の二乗を予めxmm0
に保存する。これとは対照的に
予想通り、/arch:AVX
なしのバージョンが正常に見えると:
; 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]
これは本当にバグのように見えます。誰もが別のコンパイラのバージョンでこの問題を確認することができますか?
を使用してお勧めします。私はコンソールとdevenvの両方でバージョン '16.00.40219.01 for 80x86'(MSVC2010)で問題を再現できません。いくつかのサービスパックをインストールしようとするかもしれませんが、おそらくそれはある時点で修正されました。また、 'EXX'を減らすために' x'の代わりに 'y'を使うことをお勧めします。 – stgatilov
コンパイラをアップグレードしてみてください。 MSVCには、SSE/AVX組み込み関数を誤ってコンパイルしたことがあります。 – Mysticial
64ビットモードでコンパイルしましたか? Visual Studio(私が使っているバージョン)は、デフォルトで32ビットモードになっています。 –