2012-02-14 16 views
10

私はプログラムを書いて、Intel Core i5-2500のVisual Studio 2010でx64とx86プラットフォーム用にコンパイルしました。 x64版の実行には約19秒、x86版の実行には約17秒かかります。そのような行動の理由は何でしょうか?x64プラットフォーム用にコンパイルされたC++プログラムがx86用にコンパイルされるよりも遅いのはなぜですか?

#include "timer.h" 

#include <vector> 
#include <iostream> 
#include <algorithm> 
#include <string> 
#include <sstream> 

/********************DECLARATIONS************************************************/ 
class Vector 
{ 
public: 
    Vector():x(0),y(0),z(0){} 

    Vector(double x, double y, double z) 
     : x(x) 
     , y(y) 
     , z(z) 
    { 
    } 

    double x; 
    double y; 
    double z; 
}; 


double Dot(const Vector& a, const Vector& b) 
{ 
    return a.x * b.x + a.y * b.y + a.z * b.z; 
} 


class Vector2 
{ 
public: 
    typedef double value_type; 

    Vector2():x(0),y(0){} 

    Vector2(double x, double y) 
     : x(x) 
     , y(y) 
    { 
    } 

    double x; 
    double y; 
}; 

/******************************TESTS***************************************************/ 

void Test(const std::vector<Vector>& m, std::vector<Vector2>& m2) 
{ 
    Vector axisX(0.3f, 0.001f, 0.25f); 
    Vector axisY(0.043f, 0.021f, 0.45f); 

    std::vector<Vector2>::iterator i2 = m2.begin(); 

    std::for_each(m.begin(), m.end(), 
     [&](const Vector& v) 
    { 
     Vector2 r(0,0); 
     r.x = Dot(axisX, v); 
     r.y = Dot(axisY, v); 

     (*i2) = r; 
     ++i2; 
    }); 
} 


int main() 
{ 
    cpptask::Timer timer; 

    int len2 = 300; 
    size_t len = 5000000; 
    std::vector<Vector> m; 
    m.reserve(len); 
    for (size_t i = 0; i < len; ++i) 
    { 
     m.push_back(Vector(i * 0.2345, i * 2.67, i * 0.98)); 
    } 

    /***********************************************************************************/ 
    { 
     std::vector<Vector2> m2(m.size()); 
     double time = 0; 
     for (int i = 0; i < len2; ++i) 
     { 
      timer.Start(); 
      Test(m, m2); 
      time += timer.End(); 
     } 
     std::cout << "Dot product double - " << time/len2 << std::endl; 
    } 
    /***********************************************************************************/ 


    return 0; 
} 
+1

興味深い。私はこれをCore i7 920で再現することができます。 – Mysticial

+0

XMMの組み込み関数を使用して時間を大幅に節約することができます。 –

答えて

19

短い回答:これはコンパイラの問題です。 x64オプティマイザが失敗します。


ロング回答:SSE2が無効になっている場合

このx86バージョンは非常に遅いです。しかし、私はx86でSSE2を有効にして結果を再現することができます。

あなたがその最も内側のループのアセンブリに飛び込んできた場合。 x64版には最後に2つの余分なメモリコピーがあります。

のx86:

[email protected]: 
movsd xmm2, QWORD PTR [eax-8] 
movsd xmm0, QWORD PTR [eax-16] 
movsd xmm3, QWORD PTR [eax] 
movapd xmm1, xmm0 
mulsd xmm0, QWORD PTR [email protected] 
movapd xmm7, xmm2 
mulsd xmm2, QWORD PTR [email protected] 
mulsd xmm7, xmm5 
mulsd xmm1, xmm4 
addsd xmm1, xmm7 
movapd xmm7, xmm3 
mulsd xmm3, QWORD PTR [email protected] 
mulsd xmm7, xmm6 
add eax, 24     ; 00000018H 
addsd xmm1, xmm7 
addsd xmm0, xmm2 
movq QWORD PTR [ecx], xmm1 
addsd xmm0, xmm3 
movq QWORD PTR [ecx+8], xmm0 
lea edx, DWORD PTR [eax-16] 
add ecx, 16     ; 00000010H 
cmp edx, esi 
jne SHORT [email protected] 

のx64:

[email protected]: 
movsdx xmm3, QWORD PTR [rdx-8] 
movsdx xmm5, QWORD PTR [rdx-16] 
movsdx xmm4, QWORD PTR [rdx] 
movapd xmm2, xmm3 
mulsd xmm2, xmm6 
movapd xmm0, xmm5 
mulsd xmm0, xmm7 
addsd xmm2, xmm0 
movapd xmm1, xmm4 
mulsd xmm1, xmm8 
addsd xmm2, xmm1 
movsdx QWORD PTR r$109492[rsp], xmm2 
mulsd xmm5, xmm9 
mulsd xmm3, xmm10 
addsd xmm5, xmm3 
mulsd xmm4, xmm11 
addsd xmm5, xmm4 
movsdx QWORD PTR r$109492[rsp+8], xmm5 
mov rcx, QWORD PTR r$109492[rsp] 
mov QWORD PTR [rax], rcx 
mov rcx, QWORD PTR r$109492[rsp+8] 
mov QWORD PTR [rax+8], rcx 
add rax, 16 
add rdx, 24 
lea rcx, QWORD PTR [rdx-16] 
cmp rcx, rbx 
jne SHORT [email protected] 

x64バージョンが多くの(原因不明の)を有し、ループの終わりに移動します。これは、ある種のメモリからメモリへのデータコピーのように見えます。

EDIT:

それは、x64オプティマイザは、次のコピーを最適化することができないことが判明:

(*i2) = r; 

内側のループは、2つの余分なメモリコピーを持っている理由です。ループを次のように変更した場合:

std::for_each(m.begin(), m.end(), 
    [&](const Vector& v) 
{ 
    i2->x = Dot(axisX, v); 
    i2->y = Dot(axisY, v); 
    ++i2; 
}); 

これでコピーが削除されます。今、x64バージョンは同じくらい速いx86バージョンなどです。

x86: 0.0249423 
x64: 0.0249348 

レッスンは学んだ:コンパイラは完璧ではありません。

+0

私はそうは思わない...しかし、32ビットアーチ用にコンパイルされた場合は、64ビット用にコンパイルされた場合は64ビットであるのに対して、64ビットであれば、「倍精度」である。私は長い間、サイズが変わると信じています。私はそれをチェックしますが、Visual Studioは32ビットのコンパイルだけを可能にしています。 doubleは両方で64ビット(8バイト)でなければなりません。 – David

+0

Nahの 'double'は、x86の標準IEEE倍精度です。ここのアセンブリはかなり明確です。スカラー倍精度SSEです。 – Mysticial

+0

これは同じ問題のようです:http://www.dreamincode.net/forums/topic/127989-compiler-made-optimizations/それを修正するには、/ O2コンパイラの最適化を使用しました。その結果、64ビットバージョンが高速になりました32ビット版よりも優れています。あなたはそれを試して、それが役立つかどうか確認できますか? – David

-2

通常64ビットは、(64ビット機能を特に利用しないコードの場合)32ビットより少し遅いです。 1つの特定の問題は、ポインタが大きくなり、キャッシュ内に保持できる量が減少することです。

+0

それは本当かもしれませんが、ポインタはどこにありますか?私は大規模なベクトルと浮動小数点演算に多くのメモリアクセスを参照してください。私は周りのポインタを渡す消費されているメモリ帯域幅の多くを参照してくださいしないでください。 –

+0

しかし、なぜこの記事http://msdn.microsoft.com/en-us/library/windows/desktop/ee418798%28v=vs.85%29.aspxでは、x64アーキテクチャで浮動小数点演算が改善されたと言われています。 – KolKir

+3

x86_64 ISAには、x86とは言えないSSE + SSE2が含まれています。したがって、SSEを使用せずに実行する必要があるため、手作業でASMを使用せずに、SSEageごとにcpuid検出と独立したinsnブロックを使用せずに、最低限の共通分母命令でのみ生成されたバイナリと、おそらくマイクロソフトが指しているものです。 –

0

私はあなたの質問に答えていないが、私はそれを言及する価値があると思う:

あなたが自分でベクトルクラスを書くべきではありません。固定長ベクトルの場合は、ドットを組み込んだboost::Arrayまたはcv::Vec2d and cv::Vec3dを使用してください。また、+、 - などの高速関数も使用してください(また、cv :: Vec <タイプ、長さ>)。