2016-09-17 235 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&,以尽量减少临时和堆栈副本。