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 操作の使用によって得られる速度を簡単に圧倒する可能性があるように私には思えます。

生成されたアセンブリを見ないと、何が起こっているのかはっきりとはわかりませんが、考えられる 2 つの形式のオーバーヘッドを次に示します。

関数の呼び出し (インライン化されていない場合) には、 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 を学習しているだけの場合は、構造体を使用せずに生の組み込み関数のみを使用することをお勧めします。この場合、何が起こっているかを確認し、パフォーマンスを最適に調整することが非常に簡単になります。組み込み関数を使用したコーディングは、アセンブラーで直接コーディングするのとほぼ同じですが、コンパイラーがレジスターの割り当てを行い、メモリーのロード/ストア自体を管理するという違いのみがあります。

ラッパー クラスについて言えば、いくつかの問題があります。

  1. tempポインターを削除します。常に動き回る不要なデータを追加します。
  2. デフォルトのコンストラクターを削除します。ほとんどの場合、新しい変数を宣言するたびに時間を無駄にしたくないでしょう。また、デストラクタ、コピー/移動コンストラクタ、および割り当てを実装しないでください。それらは最後に遅くなるだけです。
  3. ヘッダー ファイルですべての演算子を定義します (つまり、関数本体を記述します)。オペレーターの実装をcppファイルに記述すると、コンパイラーがそれらをインライン化するのを妨げる可能性があります (リンク時の最適化を使用しない限り、たとえばこれを参照してください)。
  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 に答える
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 に答える