Каковы последствия такого наследования?
Я работаю с библиотекой DirectXMath (или XNAMath) (определенной в заголовке DirectXMath.h пакета Windows SDK), так как она кажется действительно производительной и предлагает все, что необходимо для физики и рендеринга. Однако я нашел его довольно многословным (использование 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, который является типом SIMD, который DirectXMath использует для вычислений. Я намерен сделать это для каждой структуры хранения, предлагаемой DirectXMath.
Perfomance - это действительно важный фактор для математической библиотеки, поэтому мой вопрос: каковы последствия такого наследования? Есть ли дополнительный код, сгенерированный (конечно, при условии полной оптимизации) по сравнению с обычным использованием библиотеки?
Интуитивно я бы сказал, что сгенерированный код должен быть точно таким же, как когда я использую подробный вариант без этих вспомогательных операторов, поскольку я по сути просто переименовываю структуры и функции. Но, может быть, есть аспекты, о которых я не знаю?
PS Меня немного беспокоит тип возврата оператора присваивания, так как он добавляет дополнительный код. Было бы хорошей идеей пропустить возвращаемую ссылку, чтобы оптимизировать ее?
1 ответ
Если вы обнаружите, что DirectXMath слишком многословен для ваших вкусов, взгляните на SimpleMath в наборе инструментов DirectX. В частности, 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 в реестре.
Еще одним следствием является то, что параметр передает оболочку, как Vector2
или ваш Vector2F
никогда не будет особенно эффективным. Вся причина в том, что XMVECTOR
является typedef для __m128
а не структура, и существование FXMVECTOR
, GXMVECTOR
, HXMVECTOR
, а также CXMVECTOR
это попытка оптимизировать все возможные сценарии соглашения о вызовах и в лучшем случае получить поведение передачи в регистре (если что-то не встроено). Смотрите MSDN. Действительно лучшее, что вы можете сделать с Vector2
это последовательно передавать его const&
минимизировать временные и стековые копии.