0

好的,所以我一直在将运算符重载与一些 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 的了解,因此用外部库替换它不会有太大帮助。

4

3 回答 3

0

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

如果不查看生成的程序集,我不能肯定地说发生了什么,但这里有两种可能的开销形式。

调用一个函数(除非它是内联的)涉及 acall和 a ret,并且很可能是 apush和 apop等。以创建堆栈帧。

您要求_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;
}
于 2013-08-20T02:27:04.807 回答
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;
于 2013-08-20T05:58:59.557 回答
0

如果您只是在学习 SSE,我建议您只使用不带任何结构的原始内在函数。在这种情况下,您将更容易看到正在发生的事情,并将性能调整到最佳状态。使用内在函数编码与直接在汇编器中编码几乎相同,唯一的区别是编译器会进行寄存器分配并自行管理内存加载/存储。

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

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

这是为了更好的性能和可用性而重写的代码:

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

PS在任何情况下,您都应该定期检查编译器生成的程序集,以查看它是否存在任何性能问题。

于 2015-09-12T13:27:37.710 回答