3

私は SSE を使用して Julia セットの視覚化を行っています。これが私のコードクラスと演算子です

class vec4 {
    public:
        inline vec4(void) {}
        inline vec4(__m128 val) :v(val) {}

        __m128 v;

        inline void operator=(float *a) {v=_mm_load_ps(a);}
        inline vec4(float *a) {(*this)=a;} 
        inline vec4(float a) {(*this)=a;}

        inline void operator=(float a) {v=_mm_load1_ps(&a);}

};

inline vec4 operator+(const vec4 &a,const vec4 &b) { return _mm_add_ps(a.v,b.v); }
inline vec4 operator-(const vec4 &a,const vec4 &b) { return _mm_sub_ps(a.v,b.v); }
inline vec4 operator*(const vec4 &a,const vec4 &b) { return _mm_mul_ps(a.v,b.v); }
inline vec4 operator/(const vec4 &a,const vec4 &b) { return _mm_div_ps(a.v,b.v); }
inline vec4 operator++(const vec4 &a)
{
    __declspec(align(16)) float b[4]={1.0f,1.0f,1.0f,1.0f};
    vec4 B(b);
    return _mm_add_ps(a.v,B.v); 
}

関数自体:

vec4 TWO(2.0f);
vec4 FOUR(4.0f);
vec4 ZER(0.0f);

vec4 CR(cR);
vec4 CI(cI);

for (int i=0; i<320; i++) //H
{
    float *pr = (float*) _aligned_malloc(4 * sizeof(float), 16); //dynamic

    __declspec(align(16)) float pi=i*ratioY + startY;

    for (int j=0; j<420; j+=4) //W
    {

        pr[0]=j*ratioX + startX;
        for(int x=1;x<4;x++)
        {
            pr[x]=pr[x-1]+ratioX;
        }

        vec4 ZR(pr);
        vec4 ZI(pi);

        __declspec(align(16)) float color[4]={0.0f,0.0f,0.0f,0.0f};

        vec4 COLOR(color);
        vec4 COUNT(0.0f);

        __m128 MASK=ZER.v;

        int _count;
        enum {max_count=100};
        for (_count=0;_count<=max_count;_count++) 
        {

            vec4 tZR=ZR*ZR-ZI*ZI+CR;
            vec4 tZI=TWO*ZR*ZI+CI;
            vec4 LEN=tZR*tZR+tZI*tZI;

            __m128 MASKOLD=MASK;
            MASK=_mm_cmplt_ps(LEN.v,FOUR.v);

            ZR=_mm_or_ps(_mm_and_ps(MASK,tZR.v),_mm_andnot_ps(MASK,ZR.v));
            ZI=_mm_or_ps(_mm_and_ps(MASK,tZI.v),_mm_andnot_ps(MASK,ZI.v));

            __m128 CHECKNOTEQL=_mm_cmpneq_ps(MASK,MASKOLD);    
            COLOR=_mm_or_ps(_mm_and_ps(CHECKNOTEQL,COUNT.v),_mm_andnot_ps(CHECKNOTEQL,COLOR.v));

            COUNT=COUNT++;
            operations+=17;

            if (_mm_movemask_ps((LEN-FOUR).v)==0) break; 
        }
        _mm_store_ps(color,COLOR.v);

SSE は 553k の操作 (mull、add、if) を必要とし、タスクを完了するのに ~320ms かかりますが、通常の関数は 1428k の操作を必要としますが、計算には ~90ms しか必要ありませんか? 私は vs2010 パフォーマンス アナライザーを使用しましたが、すべての数学演算子の実行速度が非常に遅いようです。私が間違っていることは何ですか?

4

2 に答える 2

8

あなたが抱えている問題は、SSE 組み込みが非 SSE バージョンよりもはるかに多くのメモリ操作を行っていることです。あなたのベクトルクラスを使用して、私はこれを書きました:

int main (int argc, char *argv [])
{
  vec4 a (static_cast <float> (argc));
  cout << "argc = " << argc << endl;
  a=++a;
  cout << "a = (" << a.v.m128_f32 [0] << ", " << a.v.m128_f32 [1] << ", " << a.v.m128_f32 [2] << ", " << a.v.m128_f32 [3] << ", " << ")" << endl;
}

これにより、リリース ビルドで次の操作が生成されました (SSE のみを表示するように編集しました)。

fild        dword ptr [ebp+8] // load argc into FPU
fstp        dword ptr [esp+10h] // save argc as a float

movss       xmm0,dword ptr [esp+10h] // load argc into SSE
shufps      xmm0,xmm0,0 // copy argc to all values in SSE register
movaps      xmmword ptr [esp+20h],xmm0 // save to stack frame

fld1 // load 1 into FPU
fst         dword ptr [esp+20h] 
fst         dword ptr [esp+28h] 
fst         dword ptr [esp+30h] 
fstp        dword ptr [esp+38h] // create a (1,1,1,1) vector
movaps      xmm0,xmmword ptr [esp+2Ch] // load above vector into SSE
addps       xmm0,xmmword ptr [esp+1Ch] // add to vector a
movaps      xmmword ptr [esp+38h],xmm0 // save back to a

注: アドレスは ESP に相対的であり、同じ値のオフセットの奇妙な変更を説明するいくつかのプッシュがあります。

次に、コードをこのバージョンと比較します。

int main (int argc, char *argv [])
{
  float a[4];
  for (int i = 0 ; i < 4 ; ++i)
  {
    a [i] = static_cast <float> (argc + i);
  }
  cout << "argc = " << argc << endl;
  for (int i = 0 ; i < 4 ; ++i)
  {
    a [i] += 1.0f;
  }
  cout << "a = (" << a [0] << ", " << a [1] << ", " << a [2] << ", " << a [3] << ", " << ")" << endl;
}

コンパイラは上記のためにこのコードを作成しました(再び、編集され、奇妙なオフセットで)

fild        dword ptr [argc] // converting argc to floating point values
fstp        dword ptr [esp+8] 
fild        dword ptr [esp+4] // the argc+i is done in the integer unit
fstp        dword ptr [esp+0Ch] 
fild        dword ptr [esp+8] 
fstp        dword ptr [esp+18h]
fild        dword ptr [esp+10h]
fstp        dword ptr [esp+24h] // array a now initialised

fld         dword ptr [esp+8] // load a[0]
fld1 // load 1 into FPU
fadd        st(1),st // increment a[0]
fxch        st(1)
fstp        dword ptr [esp+14h] // save a[0]
fld         dword ptr [esp+1Ch] // load a[1]
fadd        st,st(1) // increment a[1]
fstp        dword ptr [esp+24h] // save a[1]
fld         dword ptr [esp+28h] // load a[2]
fadd        st,st(1) // increment a[2]
fstp        dword ptr [esp+28h]  // save a[2]
fadd        dword ptr [esp+2Ch] // increment a[3]
fstp        dword ptr [esp+2Ch] // save a[3]

メモリ アクセスに関しては、インクリメントには次のものが必要です。

SSE                  FPU
4xfloat write        1xfloat read
1xsse read           1xfloat write
1xsse read+add       1xfloat read
1xsse write          1xfloat write
                     1xfloat read
                     1xfloat write
                     1xfloat read
                     1xfloat write

total
8 float reads        4 float reads
8 float writes       4 float writes

これは、SSE が FPU バージョンの 2 倍のメモリ帯域幅を使用しており、メモリ帯域幅が主要なボトルネックであることを示しています。

SSE を真剣に最大化したい場合は、単一の SSE アセンブラー関数にアグロリズム全体を記述して、メモリの読み取り/書き込みを可能な限り排除できるようにする必要があります。組み込み関数を使用することは、最適化のための理想的なソリューションではありません。

于 2012-04-11T09:58:36.910 に答える
0

これは別の例 (マンデルブロー集合) であり、これはhttp://www.iquilezles.org/www/articles/sse/に基づくジュリア集合アルゴリズムhttp://pastebin.com/J90paPVCの実装方法とほぼ同じです 。 sse.htm。同じ話 FPU>SSE 関係のない操作をスキップすることさえあります。それを正しく行う方法はありますか?

于 2012-04-11T17:37:28.943 に答える