2016-09-17 8 views
2

Je travaille avec la bibliothèque DirectXMath (ou XNAMath) (définie dans l'en-tête DirectXMath.h du SDK Windows), car elle semble vraiment performante et offre tout ce qui est nécessaire pour la physique et le rendu. Cependant, je l'ai trouvé assez verbeux (Utiliser XMStoreFloatX et XMLoadFloatX partout est fatigant). J'essaye de rendre un peu plus facile à utiliser et ai eu l'idée de cacher les magasins/charges dans les opérateurs d'affectation/opérateurs de conversion. Comme ces deux sont nécessaires pour être des fonctions membres, je suis venu avec ce code comme exemple:Quelles sont les implications de performance d'un héritage comme celui-ci?

struct Vector2F : public DirectX::XMFLOAT2 { 
    inline Vector2F() : DirectX::XMFLOAT2() {}; 
    inline Vector2F(float x, float y) : DirectX::XMFLOAT2(x, y) {}; 
    inline Vector2F(float const * pArray) : DirectX::XMFLOAT2(pArray) {}; 

    inline Vector2F(DirectX::XMVECTOR vector) { 
     DirectX::XMStoreFloat2(this, vector); 
    } 
    inline Vector2F& __vectorcall operator= (DirectX::XMVECTOR vector) { 
     DirectX::XMStoreFloat2(this, vector); 
     return *this; 
    } 

    inline __vectorcall operator DirectX::XMVECTOR() { 
     return DirectX::XMLoadFloat2(this); 
    } 
}; 

Comme vous pouvez le voir reproduit l'interface publique de XMFLOAT2 et ajoute un constructeur, un opérateur d'affectation et une conversion pour XMVECTOR, qui est le type SIMD DirectXMath utilise pour les calculs. J'ai l'intention de le faire pour chaque structure de stockage proposée par DirectXMath. La performance est un facteur très important pour une librairie mathématique, donc ma question est: Quelles sont les implications de performance d'un tel héritage? Y at-il un code supplémentaire généré (bien sûr en supposant une optimisation complète) par rapport à l'utilisation normale de la bibliothèque? Intuitivement, je dirais que le code généré doit être exactement le même que lorsque j'utilise la variante verbeuse sans ces opérateurs de commodité, car je ne fais que renommer les structures et les fonctions. Mais peut-être y a-t-il des aspects que je ne connais pas?


P.S. Je suis un peu préoccupé par le type de retour de l'opérateur d'affectation, car il ajoute du code supplémentaire. Serait-ce une bonne idée d'omettre la référence revenant pour l'optimiser?

+2

Je pense que vous trouverez que dans le code optimisé, la pénalité de performance sera nulle. –

+0

Si la valeur renvoyée n'est pas utilisée, le compilateur optimisera très probablement 'return * this;'. C'est un idiome commun que les compilateurs connaissent bien. –

+0

Vous avez deux conversions implicites: 1) à partir de XMVECTOR et 2) à XMVECTOR. C'est une grande possibilité d'ambiguïté. Ne faites pas ça (n'utilisez pas Vector2F). –

Répondre

2

Si vous trouvez que DirectXMath est un peu trop verbeux pour vos goûts, jetez un oeil à SimpleMath dans le DirectX Tool Kit. En particulier, la classe Vector2:

struct Vector2 : public XMFLOAT2 
{ 
    Vector2() : XMFLOAT2(0.f, 0.f) {} 
    explicit Vector2(float x) : XMFLOAT2(x, x) {} 
    Vector2(float _x, float _y) : XMFLOAT2(_x, _y) {} 
    explicit Vector2(_In_reads_(2) const float *pArray) : XMFLOAT2(pArray) {} 
    Vector2(FXMVECTOR V) { XMStoreFloat2(this, V); } 
    Vector2(const XMFLOAT2& V) { this->x = V.x; this->y = V.y; } 
    explicit Vector2(const XMVECTORF32& F) { this->x = F.f[0]; this->y = F.f[1]; } 

    operator XMVECTOR() const { return XMLoadFloat2(this); } 

    // Comparison operators 
    bool operator == (const Vector2& V) const; 
    bool operator != (const Vector2& V) const; 

    // Assignment operators 
    Vector2& operator= (const Vector2& V) { x = V.x; y = V.y; return *this; } 
    Vector2& operator= (const XMFLOAT2& V) { x = V.x; y = V.y; return *this; } 
    Vector2& operator= (const XMVECTORF32& F) { x = F.f[0]; y = F.f[1]; return *this; } 
    Vector2& operator+= (const Vector2& V); 
    Vector2& operator-= (const Vector2& V); 
    Vector2& operator*= (const Vector2& V); 
    Vector2& operator*= (float S); 
    Vector2& operator/= (float S); 

    // Unary operators 
    Vector2 operator+() const { return *this; } 
    Vector2 operator-() const { return Vector2(-x, -y); } 

    // Vector operations 
    bool InBounds(const Vector2& Bounds) const; 

    float Length() const; 
    float LengthSquared() const; 

    float Dot(const Vector2& V) const; 
    void Cross(const Vector2& V, Vector2& result) const; 
    Vector2 Cross(const Vector2& V) const; 

    void Normalize(); 
    void Normalize(Vector2& result) const; 

    void Clamp(const Vector2& vmin, const Vector2& vmax); 
    void Clamp(const Vector2& vmin, const Vector2& vmax, Vector2& result) const; 

    // Static functions 
    static float Distance(const Vector2& v1, const Vector2& v2); 
    static float DistanceSquared(const Vector2& v1, const Vector2& v2); 

    static void Min(const Vector2& v1, const Vector2& v2, Vector2& result); 
    static Vector2 Min(const Vector2& v1, const Vector2& v2); 

    static void Max(const Vector2& v1, const Vector2& v2, Vector2& result); 
    static Vector2 Max(const Vector2& v1, const Vector2& v2); 

    static void Lerp(const Vector2& v1, const Vector2& v2, float t, Vector2& result); 
    static Vector2 Lerp(const Vector2& v1, const Vector2& v2, float t); 

    static void SmoothStep(const Vector2& v1, const Vector2& v2, float t, Vector2& result); 
    static Vector2 SmoothStep(const Vector2& v1, const Vector2& v2, float t); 

    static void Barycentric(const Vector2& v1, const Vector2& v2, const Vector2& v3, float f, float g, Vector2& result); 
    static Vector2 Barycentric(const Vector2& v1, const Vector2& v2, const Vector2& v3, float f, float g); 

    static void CatmullRom(const Vector2& v1, const Vector2& v2, const Vector2& v3, const Vector2& v4, float t, Vector2& result); 
    static Vector2 CatmullRom(const Vector2& v1, const Vector2& v2, const Vector2& v3, const Vector2& v4, float t); 

    static void Hermite(const Vector2& v1, const Vector2& t1, const Vector2& v2, const Vector2& t2, float t, Vector2& result); 
    static Vector2 Hermite(const Vector2& v1, const Vector2& t1, const Vector2& v2, const Vector2& t2, float t); 

    static void Reflect(const Vector2& ivec, const Vector2& nvec, Vector2& result); 
    static Vector2 Reflect(const Vector2& ivec, const Vector2& nvec); 

    static void Refract(const Vector2& ivec, const Vector2& nvec, float refractionIndex, Vector2& result); 
    static Vector2 Refract(const Vector2& ivec, const Vector2& nvec, float refractionIndex); 

    static void Transform(const Vector2& v, const Quaternion& quat, Vector2& result); 
    static Vector2 Transform(const Vector2& v, const Quaternion& quat); 

    static void Transform(const Vector2& v, const Matrix& m, Vector2& result); 
    static Vector2 Transform(const Vector2& v, const Matrix& m); 
    static void Transform(_In_reads_(count) const Vector2* varray, size_t count, const Matrix& m, _Out_writes_(count) Vector2* resultArray); 

    static void Transform(const Vector2& v, const Matrix& m, Vector4& result); 
    static void Transform(_In_reads_(count) const Vector2* varray, size_t count, const Matrix& m, _Out_writes_(count) Vector4* resultArray); 

    static void TransformNormal(const Vector2& v, const Matrix& m, Vector2& result); 
    static Vector2 TransformNormal(const Vector2& v, const Matrix& m); 
    static void TransformNormal(_In_reads_(count) const Vector2* varray, size_t count, const Matrix& m, _Out_writes_(count) Vector2* resultArray); 

    // Constants 
    static const Vector2 Zero; 
    static const Vector2 One; 
    static const Vector2 UnitX; 
    static const Vector2 UnitY; 
}; 

// Binary operators 
Vector2 operator+ (const Vector2& V1, const Vector2& V2); 
Vector2 operator- (const Vector2& V1, const Vector2& V2); 
Vector2 operator* (const Vector2& V1, const Vector2& V2); 
Vector2 operator* (const Vector2& V, float S); 
Vector2 operator/ (const Vector2& V1, const Vector2& V2); 
Vector2 operator* (float S, const Vector2& V); 

La principale raison pour laquelle DirectXMath est si bavard en premier lieu est de le rendre très clair pour le programmeur lorsque « renverser la mémoire », car cela a tendance à avoir un impact négatif sur les performances des Code SIMD. Quand je suis passé de XNAMath à DirectXMath, j'avais envisagé d'ajouter quelque chose comme les conversions implicites que j'ai utilisées pour "SimpleMath", mais je voulais m'assurer que de telles "magies C++" étaient opt-in et jamais une surprise pour une performance sensible. développeur. SimpleMath agit également un peu comme des roues d'entraînement, ce qui facilite le portage du code existant qui ne tient pas compte de l'alignement et le transforme en quelque chose de plus SIMD-friendly au fil du temps. Le vrai problème de performance avec SimpleMath (et votre wrapper) est que chaque implémentation de la fonction doit faire un chargement explicite & Stocker autour de ce qui est par ailleurs une quantité relativement faible de SIMD. Idéalement, dans un code optimisé, tout serait fusionné, mais dans le code de débogage, ils sont toujours là. Pour tout gain de performance réel de SIMD, vous voulez avoir de longues séries d'opérations SIMD dans le registre entre chaque paire de magasins Load &.

Une autre implication est que le paramètre passant un wrapper comme Vector2 ou votre Vector2F ne sera jamais particulièrement efficace. La raison pour laquelle XMVECTOR est un typedef pour __m128 plutôt qu'une struct, et l'existence de FXMVECTOR, GXMVECTOR, HXMVECTOR, et CXMVECTOR est d'essayer d'optimiser tous les scénarios de convention d'appel possibles et dans le meilleur des cas obtenir un comportement de passage en registre (si les choses ne s'introduisent pas).Voir MSDN. Vraiment le meilleur que vous pouvez faire avec Vector2 est de toujours le passer const& pour minimiser les temporaires et empiler des copies.