Question Pourquoi le programme c ++ compilé pour la plate-forme x64 est-il plus lent que celui compilé pour x86?


J'ai écrit un programme et l'ai compilé pour une plate-forme x64 et x86 dans Visual Studio 2010 sur Intel Core i5-2500. La version x64 prend environ 19 secondes pour l'exécution et x86 prend environ 17 secondes. Quelle peut être la raison d'un tel comportement?

#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;
}

10
2018-02-14 20:28


origine


Réponses:


Réponse courte: C'est un problème de compilateur. L'optimiseur x64 échoue.


Longue réponse:

Cette version x86 est très lente si SSE2 est désactivé. Mais je suis capable de reproduire les résultats avec SSE2 activé dans x86.

Si vous plongez dans l'assemblage de cette boucle la plus interne. La version x64 dispose de deux copies de mémoire supplémentaires à la fin.

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

x64:

$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

La version x64 a beaucoup plus de déplacements (inexpliqués) à la fin de la boucle. Cela ressemble à une sorte de copie de données mémoire-mémoire.

MODIFIER:

Il s'avère que l'optimiseur x64 ne peut pas optimiser la copie suivante:

(*i2) = r;

C'est pourquoi la boucle interne a deux copies de mémoire supplémentaires. Si vous changez la boucle en ceci:

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

Cela élimine les copies. Maintenant, la version x64 est aussi rapide que la version x86:

x86: 0.0249423
x64: 0.0249348

Leçon apprise: Les compilateurs ne sont pas parfaits.


19
2018-02-14 20:45



Je ne réponds pas à votre question mais je pense qu'il vaut la peine de mentionner:

Vous ne devriez pas écrire de classes vectorielles par vous-même. Pour les vecteurs de longueur fixe, utilisez plutôt boost :: Array ou cv :: Vec2d et cv :: Vec3d qui a construit dans le point et d'autres fonctions rapides comme l'opération +, - etc ... (aussi cv :: Vec <type, longueur> est offert)


0
2017-10-30 12:07



64 bits est normalement un peu plus lent que 32 bits (pour le code qui ne tire pas avantage des fonctionnalités 64 bits). Un problème particulier est que les pointeurs sont plus gros, ce qui réduit la quantité de données pouvant être conservée dans le cache.


-2
2018-02-14 20:39