个人SSE库

时间:2013-08-20 02:02:38

标签: c++ gcc x86 sse simd

好的,所以我一直在使用一些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,因此用外部库替换它不会有太大帮助。

3 个答案:

答案 0 :(得分:0)

在我看来,您引入的开销量很容易超过您通过使用SSE操作获得的任何速度。

不看生产的组件,我无法确切地说出发生了什么,但这里有两种可能的开销形式。

调用函数(除非它是内联的)涉及callret,并且很可能是pushpop等...来创建堆栈帧。

您为每项操作调用_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,我建议只使用没有任何结构的原始内在函数。在这种情况下,您可以更轻松地查看正在发生的事情,并将性能调整到最佳状态。使用内在函数进行编码与直接在汇编程序中进行编码几乎相同,唯一的区别是编译器执行寄存器分配并管理内存加载/存储本身。

说到你的包装类,它有几个问题:

  1. 删除temp指针。 它增加了不必要的数据,不断移动。
  2. 删除默认构造函数。 在大多数情况下,每次声明新变量时都不想浪费时间。并且实现析构函数,复制/移动构造函数和赋值:它们只会在最后减慢你的速度。
  3. 在头文件中定义(即编写函数体)所有运算符。 如果您在 cpp 文件中编写运算符的实现,则可能会阻止编译器内联它们(除非您使用链接时优化,请参阅this)。
  4. 尽可能按值接受sse类型的参数。 如果您通过float*,那么您可能需要从此指针加载值。但是,在大多数情况下,没有必要:数据已经注册。当您使用类型__m128的值时,编译器可以自行决定是否必须将数据保存/加载到内存中。
  5. 从每个非修改运算符返回类型sse的值。 现在,您将结果存储到内存指针中,该指针以丑陋的方式实现。这会强制编译器将数据真正存储到内存中,而不是简单地将值保存在寄存器中。按值返回__m128时,编译器cam会决定何时保存/加载数据。
  6. 以下是为了更好的性能和可用性而重写的代码:

    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。在任何情况下,您都应该定期检查编译器生成的程序集,以查看它是否存在任何性能问题。