2015-09-26 8 views
5

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] 

これは本当にバグのように見えます。誰もが別のコンパイラのバージョンでこの問題を確認することができますか?

+0

を使用してお勧めします。私はコンソールとdevenvの両方でバージョン '16.00.40219.01 for 80x86'(MSVC2010)で問題を再現できません。いくつかのサービスパックをインストールしようとするかもしれませんが、おそらくそれはある時点で修正されました。また、 'EXX'を減らすために' x'の代わりに 'y'を使うことをお勧めします。 – stgatilov

+1

コンパイラをアップグレードしてみてください。 MSVCには、SSE/AVX組み込み関数を誤ってコンパイルしたことがあります。 – Mysticial

+0

64ビットモードでコンパイルしましたか? Visual Studio(私が使っているバージョン)は、デフォルトで32ビットモードになっています。 –

答えて

0

代わりに、手動で水平方向の加算を行うの(私は現在、コンパイラを更新する権限を持っていない)、私はそれが私にはバグのように見える、対応するSSE命令_mm_hadd_ps

// Final reduction 
__m128 sum1 = _mm_hadd_ps(mEX, mEXX); 
     // == {EX[0]+EX[1], EX[2]+EX[3], EXX[0]+EXX[1], EXX[2]+EXX[3]} 
// final sum and conversion to double: 
__m128d sum2 = _mm_cvtps_pd(_mm_hadd_ps(sum1, sum1)); 
// result vector: 
double dEX_EXX[2]; // (I don't know MSVC syntax for stack aligned arrays) 
// store register to stack: (should be _mm_store_pd, if the array is aligned) 
_mm_storeu_pd(dEX_EXX, sum2); 
std::cout << "EX: " << dEX_EXX[0] << "\nEXX: " << dEX_EXX[1] << std::endl; 
+0

これは実際にはSSE3命令です。これは非常に広範にサポートされています(ゲーマーの場合、Valve's Hardware調査では99%となっています)が、サポートを明示的に確認し、そうでない場合はシステムが古すぎるとユーザーに伝える必要があります。 SSE3の詳細については、[こちらの記事](https://blogs.msdn.microsoft.com/chuckw/2012/09/11/directxmath-sse3-and-ssse3/)を参照してください。最新のPCでは、SSEとSSE2が普遍的にサポートされています - 少なくとも12歳以上のハードウェアを使用している新興市場の外では、SSE3はまだ普遍的ではありません。 –

関連する問題