C++ 为什么是c++;为x64平台编译的程序比为x86平台编译的程序慢?

C++ 为什么是c++;为x64平台编译的程序比为x86平台编译的程序慢?,c++,performance,optimization,64-bit,double-precision,C++,Performance,Optimization,64 Bit,Double Precision,我已经编写了这个程序,并在Visual Studio 2010的Intel Core i5-2500上为x64和x86平台编译了它。x64版本的执行时间约为19秒,x86版本的执行时间约为17秒。这种行为的原因是什么 #include "timer.h" #include <vector> #include <iostream> #include <algorithm> #include <string> #include <sstream

我已经编写了这个程序,并在Visual Studio 2010的Intel Core i5-2500上为x64和x86平台编译了它。x64版本的执行时间约为19秒,x86版本的执行时间约为17秒。这种行为的原因是什么

#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;
}
#包括“timer.h”
#包括
#包括
#包括
#包括
#包括
/********************声明************************************************/
类向量
{
公众:
向量():x(0),y(0),z(0){}
向量(双x,双y,双z)
:x(x)
,y(y)
,z(z)
{
}
双x;
双y;
双z;
};
双点(常数向量和a、常数向量和b)
{
返回a.x*b.x+a.y*b.y+a.z*b.z;
}
类向量2
{
公众:
typedef双值_类型;
向量2():x(0),y(0){}
矢量2(双x,双y)
:x(x)
,y(y)
{
}
双x;
双y;
};
/******************************测验***************************************************/
无效测试(常数std::vector&m、std::vector&m2)
{
矢量轴x(0.3f,0.001f,0.25f);
矢量轴(0.043f,0.021f,0.45f);
std::vector::迭代器i2=m2.begin();
标准::对于每个(m.开始(),m.结束(),
[&](常数向量&v)
{
向量2r(0,0);
r、 x=点(轴x,v);
r、 y=点(轴,v);
(*i2)=r;
++i2;
});
}
int main()
{
cpptask::计时器;
int len2=300;
尺寸长度=5000000;
std::向量m;
m、 储备(len);
对于(尺寸i=0;istd::cout64位通常比32位慢一点(对于特别不利用64位功能的代码)。一个特殊的问题是指针更大,减少了缓存中可以保存的数量。

简短回答:这是编译器的问题。x64优化器失败


长答案:

$LL71@main:
movsd   xmm2, QWORD PTR [eax-8]
movsd   xmm0, QWORD PTR [eax-16]
movsd   xmm3, QWORD PTR [eax]
movapd  xmm1, xmm0
mulsd   xmm0, QWORD PTR __real@3fa60418a0000000
movapd  xmm7, xmm2
mulsd   xmm2, QWORD PTR __real@3f95810620000000
mulsd   xmm7, xmm5
mulsd   xmm1, xmm4
addsd   xmm1, xmm7
movapd  xmm7, xmm3
mulsd   xmm3, QWORD PTR __real@3fdcccccc0000000
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 $LL71@main
$LL175@main:
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 $LL175@main
如果禁用SSE2,这个x86版本会非常慢。但是我可以在x86中启用SSE2的情况下重现结果

如果深入到最内部循环的程序集中,x64版本的末尾有两个额外的内存副本

x86:

$LL71@main:
movsd   xmm2, QWORD PTR [eax-8]
movsd   xmm0, QWORD PTR [eax-16]
movsd   xmm3, QWORD PTR [eax]
movapd  xmm1, xmm0
mulsd   xmm0, QWORD PTR __real@3fa60418a0000000
movapd  xmm7, xmm2
mulsd   xmm2, QWORD PTR __real@3f95810620000000
mulsd   xmm7, xmm5
mulsd   xmm1, xmm4
addsd   xmm1, xmm7
movapd  xmm7, xmm3
mulsd   xmm3, QWORD PTR __real@3fdcccccc0000000
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 $LL71@main
$LL175@main:
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 $LL175@main
x64:

$LL71@main:
movsd   xmm2, QWORD PTR [eax-8]
movsd   xmm0, QWORD PTR [eax-16]
movsd   xmm3, QWORD PTR [eax]
movapd  xmm1, xmm0
mulsd   xmm0, QWORD PTR __real@3fa60418a0000000
movapd  xmm7, xmm2
mulsd   xmm2, QWORD PTR __real@3f95810620000000
mulsd   xmm7, xmm5
mulsd   xmm1, xmm4
addsd   xmm1, xmm7
movapd  xmm7, xmm3
mulsd   xmm3, QWORD PTR __real@3fdcccccc0000000
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 $LL71@main
$LL175@main:
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 $LL175@main
x64版本在循环结束时有更多(无法解释的)移动。它看起来像某种内存到内存的数据拷贝

编辑: 结果表明,x64优化器无法优化以下副本:

(*i2) = r;
这就是为什么内部循环有两个额外的内存副本。如果将循环更改为:

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

经验教训:编译器并不完美。

我没有回答您的问题,但我认为值得一提:


您不应该自己编写向量类。对于固定长度的向量,请使用或,它内置了点和其他快速函数,如操作+,-等。(还提供了cv::Vec)

很有趣。我能够在Core i7 920上重现这一点。值得注意的是,您可以使用XMM内部函数并节省更多的时间。这可能是真的,但指针在哪里?我看到了大量对大向量和浮点运算的内存访问。我没有看到大量内存带宽被指针占用。但为什么这篇文章说x64体系结构改进了浮点运算。x86_64 ISA包括SSE+SSE2,这不能说是x86。因此,生成的二进制文件只使用最小公分母指令,没有任何手工制作的ASM,也没有cpuid检测&每个SSEage都有单独的insn块——这可能就是为什么微软提到,x64在不使用SSE的情况下速度较慢。我不会对x64与x86的速度优势/劣势做任何笼统的陈述。当然,内存有限的代码可能会有所不同,但另一方面,x64有时会有一些有用的附加指令,而且通常会有更多的寄存器。虽然这两个参数对FP代码都不重要。我不认为它重要…但如果为64位编译,则为“double”64位;如果为32位arch编译,则为32位。我相信long会改变大小,但不确定double是否重要。我会检查它,但visual studio今天只允许我编译32位。double在这两个参数上都应该是64位(8字节).Nah,
double
是x86上的标准IEEE双精度。这里的汇编非常清楚,它都是标量双精度SSE。这看起来像是同一个问题:为了解决它,他们使用了/O2编译器优化,这导致64位版本比32位版本快。你能试试看它是否有用吗?我使用了/O2对我的测试进行优化(默认情况下,MSVC中的发布版本会启用此标志)。我的两个测试都是使用
/O2
完成的。但我想我现在看到了问题。x64编译器无法完全优化对
点()的函数调用。
点()的两个调用的返回值
正在通过内存而不是寄存器进行复制…奇怪。x86没有这个问题。