2016-09-17 53 views
2

我正在使用DirectXMath(或XNAMath)庫(在Windows SDK的DirectXMath.h頭文件中定義),因爲它看起來確實是高性能的,並提供了物理和渲染所需的一切。不過,我發現它非常冗長(使用XMStoreFloatX和XMLoadFloatX到處都很累人)。這樣的繼承有什麼性能影響?

我想讓它操作起來更容易一些,並想出隱藏分配運算符/轉換運算符中的存儲/加載的想法。由於這兩種需要是成員函數,我想出了這個代碼爲例:

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

正如你可以看到它複製XMFLOAT2的公共接口,並增加了一個構造函數,賦值運算符和轉換XMVECTOR是DirectXMath用於計算的SIMD類型。我打算爲DirectXMath提供的每個存儲結構執行此操作。

性能是數學庫的一個非常重要的因素,因此我的問題是:這種繼承的性能影響是什麼?與庫的正常使用相比,是否有任何其他代碼生成(當然假設完全優化)?

直覺上我會說生成的代碼應該和我在使用沒有這些便利操作符的詳細變體時完全一樣,因爲我基本上只是重命名了結構和函數。但也許有些方面我不知道?


P.S.我有點擔心賦值運算符的返回類型,因爲它增加了額外的代碼。忽略引用來優化它是否是一個好主意?

+2

我想你會發現在優化的代碼中,性能損失將爲零。 –

+0

如果沒有使用返回的值,編譯器極有可能優化'return * this;'。這是編譯器很清楚的一個常見習慣用法。 –

+0

您有兩個隱式轉換:1)從XMVECTOR和2)到XMVECTOR。這是模棱兩可的可能性很大。不要這樣做(不要使用Vector2F)。 –

回答

2

如果您發現DirectXMath對您的口味有點過於冗長,請在DirectX Tool Kit中查看SimpleMath。尤其是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); 

的主要原因DirectXMath是如此冗長首先是要當「溢出到內存」,因爲這往往的性能產生負面影響很清楚地向程序員SIMD代碼。當我從XNAMath轉移到DirectXMath時,我曾考慮過添加類似於「SimpleMath」的隱式轉換,但我想確保任何這樣的「C++魔術」都是選擇加入的,並且對於性能敏感開發商。 SimpleMath也有點像訓練輪,可以更輕鬆地移植現有的不支持對齊的代碼,並隨着時間的推移將其變爲更友好的SIMD。

SimpleMath(和你的包裝器)的實際性能問題是,每個函數的實現都必須做一個明確的加載存儲其他相當少量的SIMD。理想情況下,優化後的代碼將全部合併,但在調試代碼中,它們始終存在。對於SIMD的任何實際性能優勢,您希望在每個加載對之間進行長時間的寄存器內SIMD操作。

另一個含義是傳遞包裝如Vector2Vector2F的參數決不會特別有效。 XMVECTOR__m128的類型定義而不是結構的全部原因,FXMVECTOR,GXMVECTOR,HXMVECTORCXMVECTOR的存在是嘗試優化所有可能的調用約定場景,並在最佳情況下獲取註冊表內傳遞行爲(如果事情不內聯)。見MSDN。真的,Vector2可以做的最好的是始終通過它const&,以儘量減少臨時和堆棧副本。

相關問題