好的,所以我一直在使用一些SSE / AVX内在函数的运算符重载,以便在矢量处理很有用的更简单的情况下使用它们。类定义如下所示:
#define Float16a float __attribute__((__aligned__(16)))
class sse
{
private:
__m128 vec __attribute__((__aligned__(16)));
Float16a *temp;
public:
//=================================================================
sse();
sse(float *value);
//=================================================================
void operator + (float *param);
void operator - (float *param);
void operator * (float *param);
void operator / (float *param);
void operator % (float *param);
void operator ^ (int number);
void operator = (float *param);
void operator == (float *param);
void operator += (float *param);
void operator -= (float *param);
void operator *= (float *param);
void operator /= (float *param);
};
每个单独的功能都与:
相似void sse::operator + (float *param)
{
vec = _mm_add_ps(vec, _mm_load_ps(param));
_mm_store_ps(temp, vec);
}
到目前为止,我在编写代码时遇到了一些问题,但是我遇到了一些性能问题,当使用时,与极其微不足道的标量代码相比,SSE / AVX代码具有显着的性能提升。我知道这种类型的代码可能很困难,但我甚至不确定瓶颈到底是什么。如果有任何指针可以抛向我,我们将不胜感激。
请注意,这只是我正在编写的一个人项目,以进一步了解SSE / AVX,因此用外部库替换它不会有太大帮助。
答案 0 :(得分:0)
在我看来,您引入的开销量很容易超过您通过使用SSE操作获得的任何速度。
不看生产的组件,我无法确切地说出发生了什么,但这里有两种可能的开销形式。
调用函数(除非它是内联的)涉及call
和ret
,并且很可能是push
和pop
等...来创建堆栈帧。
您为每项操作调用_mm_store_ps
,如果您将多个操作链接在一起,则需要支付的费用超过必要的时间。
此外,您的代码中不清楚这是否有问题,但请确保temp
是有效指针。
希望有所帮助。祝你好运。
跟进评论。
不确定这是否是好的C ++,如果不是,请教育我,但是鉴于我的知识有限,这就是我的建议。如果其他人有更好的建议,我实际上会非常感兴趣。
使用我认为被称为“转换运算符”的东西,但由于你的返回不是单个浮点数而是4个浮点数,你还需要添加一个类型。
typedef struct float_data
{
float data[4];
};
class sse
{
...
float_data floatData;
...
operator float_data&();
...
};
sse::operator float_data&()
{
_mm_store_ps(floatData.data, vec);
return &float_data;
}
答案 1 :(得分:0)
这是我的SSE库的一部分。在处理海量数据时,我总是使用SoA的SoA。并且_ m128 / _m256的运算符重载可以很容易地将C / C ++算法转换为SIMD。
库不支持加载/存储,因为SSE / AVX对内存操作非常敏感。内存访问不良会导致数十个CPU周期并导致计算失败。
__forceinline __m128 operator+(__m128 l, __m128 r) { return _mm_add_ps(l,r); }
__forceinline __m128 operator-(__m128 l, __m128 r) { return _mm_sub_ps(l,r); }
__forceinline __m128 operator*(__m128 l, __m128 r) { return _mm_mul_ps(l,r); }
__forceinline __m128 operator/(__m128 l, __m128 r) { return _mm_div_ps(l,r); }
__forceinline __m128 operator&(__m128 l, __m128 r) { return _mm_and_ps(l,r); }
__forceinline __m128 operator|(__m128 l, __m128 r) { return _mm_or_ps(l,r); }
__forceinline __m128 operator<(__m128 l, __m128 r) { return _mm_cmplt_ps(l,r); }
__forceinline __m128 operator>(__m128 l, __m128 r) { return _mm_cmpgt_ps(l,r); }
__forceinline __m128 operator<=(__m128 l, __m128 r) { return _mm_cmple_ps(l,r); }
__forceinline __m128 operator>=(__m128 l, __m128 r) { return _mm_cmpge_ps(l,r); }
__forceinline __m128 operator!=(__m128 l, __m128 r) { return _mm_cmpneq_ps(l,r); }
__forceinline __m128 operator==(__m128 l, __m128 r) { return _mm_cmpeq_ps(l,r); }
__forceinline __m128 _mm_merge_ps(__m128 m, __m128 l, __m128 r)
{
return _mm_or_ps(_mm_andnot_ps(m, l), _mm_and_ps(m, r));
}
struct TPoint4
{
TPoint4() {}
TPoint4(const D3DXVECTOR3& a) :x(_mm_set1_ps(a.x)), y(_mm_set1_ps(a.y)), z(_mm_set1_ps(a.z)) {}
TPoint4(__m128 a, __m128 b, __m128 c) :x(a), y(b), z(c) {}
TPoint4(const __m128* a) :x(a[0]), y(a[1]), z(a[2]) {}
TPoint4(const D3DXVECTOR3& a, const D3DXVECTOR3& b, const D3DXVECTOR3& c, const D3DXVECTOR3& d) :x(_mm_set_ps(a.x,b.x,c.x,d.x)), y(_mm_set_ps(a.y,b.y,c.y,d.y)), z(_mm_set_ps(a.z,b.z,c.z,d.z)) {}
operator __m128* () { return &x; }
operator const __m128* () const { return &x; }
TPoint4 operator+(const TPoint4& r) const { return TPoint4(x+r.x, y+r.y, z+r.z); }
TPoint4 operator-(const TPoint4& r) const { return TPoint4(x-r.x, y-r.y, z-r.z); }
TPoint4 operator*(__m128 r) const { return TPoint4(x * r, y * r, z * r); }
TPoint4 operator/(__m128 r) const { return TPoint4(x / r, y / r, z / r); }
__m128 operator[](int index) const { return _val[index]; }
union
{
struct
{
__m128 x, y, z;
};
struct
{
__m128 _val[3];
};
};
};
__forceinline TPoint4* TPoint4Cross(TPoint4* result, const TPoint4* l, const TPoint4* r)
{
result->x = (l->y * r->z) - (l->z * r->y);
result->y = (l->z * r->x) - (l->x * r->z);
result->z = (l->x * r->y) - (l->y * r->x);
return result;
}
__forceinline __m128 TPoint4Dot(const TPoint4* l, const TPoint4* r)
{
return (l->x * r->x) + (l->y * r->y) + (l->z * r->z);
}
__forceinline TPoint4* TPoint4Normalize(TPoint4* result, const TPoint4* l)
{
__m128 rec_len = _mm_rsqrt_ps( (l->x * l->x) + (l->y * l->y) + (l->z * l->z) );
result->x = l->x * rec_len;
result->y = l->y * rec_len;
result->z = l->z * rec_len;
return result;
}
__forceinline __m128 TPoint4Length(const TPoint4* l)
{
return _mm_sqrt_ps( (l->x * l->x) + (l->y * l->y) + (l->z * l->z) );
}
__forceinline TPoint4* TPoint4Merge(TPoint4* result, __m128 mask, const TPoint4* l, const TPoint4* r)
{
result->x = _mm_merge_ps(mask, l->x, r->x);
result->y = _mm_merge_ps(mask, l->y, r->y);
result->z = _mm_merge_ps(mask, l->z, r->z);
return result;
}
extern __m128 g_zero4;
extern __m128 g_one4;
extern __m128 g_fltMax4;
extern __m128 g_mask4;
extern __m128 g_epsilon4;
答案 2 :(得分:0)
如果你刚刚学习SSE,我建议只使用没有任何结构的原始内在函数。在这种情况下,您可以更轻松地查看正在发生的事情,并将性能调整到最佳状态。使用内在函数进行编码与直接在汇编程序中进行编码几乎相同,唯一的区别是编译器执行寄存器分配并管理内存加载/存储本身。
说到你的包装类,它有几个问题:
temp
指针。
它增加了不必要的数据,不断移动。sse
类型的参数。
如果您通过float*
,那么您可能需要从此指针加载值。但是,在大多数情况下,没有必要:数据已经注册。当您使用类型__m128
的值时,编译器可以自行决定是否必须将数据保存/加载到内存中。sse
的值。
现在,您将结果存储到内存指针中,该指针以丑陋的方式实现。这会强制编译器将数据真正存储到内存中,而不是简单地将值保存在寄存器中。按值返回__m128
时,编译器cam会决定何时保存/加载数据。以下是为了更好的性能和可用性而重写的代码:
class sse {
private:
__m128 vec;
public:
explicit sse(float *ptr) { vec = _mm_loadu_ps(ptr); }
sse(__m128 reg) { vec = reg; }
void store(float *ptr) { _mm_storeu_ps(ptr, vec); }
sse operator + (sse other) const {
return sse(_mm_add_ps(vec, other.vec));
}
sse operator - (sse other) {...}
sse operator * (sse other) {...}
sse operator / (sse other) {...}
void operator += (sse other) {
vec = _mm_add_ps(vec, other.vec);
}
void operator -= (float *param) {...}
void operator *= (float *param) {...}
void operator /= (float *param) {...}
//I don't know what you mean by these operators:
//void operator ^ (int number);
//void operator == (float *param);
//sse operator % (sse other);
};
P.S。在任何情况下,您都应该定期检查编译器生成的程序集,以查看它是否存在任何性能问题。