0

私は、特定の種類の数値アルゴリズムを記述するためのコード スタイルのアイデアを持っています。このコード スタイルでは、データ レイアウトにとらわれない方法でアルゴリズムを純粋に記述します。

つまり、すべての関数は (1 つまたは複数の) スカラー引数を取り、(ポインターを介して) 1 つまたは複数のスカラー戻り値を返します。したがって、たとえば、3 つのメンバーを持つ構造体または float[3] xyz を使用する代わりに、3d float ベクトルを使用する関数がある場合、float x、float y、float z を使用します。

アイデアは、入力データと出力データのレイアウトを変更できるということです。つまり、配列の構造体と構造体データの配列の配列、キャッシュ効率のためのタイルレイアウト、SIMD とマルチコアの粒度などで遊ぶことができます...データ レイアウトのすべての組み合わせについて、すべてのコードを書き直す必要があります。

この戦略にはいくつかの明らかな欠点があります。

  • 関数内で for ループを使用してコードをよりコンパクトにすることはできません
  • 関数のシグネチャには、より多くのパラメーターが必要です

...しかし、配列が短い場合、それらは口当たりが良く、高速にするためにコードを何度も書き直す必要がなくなります。

しかし特に、コンパイラが x+=a; のようなものを扱えないのではないかと心配しています。y+=b; z+=c; インライン化された関数のスタックの一番上で SIMD を実行するのではなく、呼び出しスタックの一番下で SIMD を実行したい場合は、w+=d を使用して単一の SIMD ベクトル add に自動ベクトル化します。

clang および/または gcc は、C および/または C++ コードで (おそらく関数がインライン化された後) 手動でアンロールされたループを「再ロール」し、ベクトル化されたマシン コードを生成できますか?

4

2 に答える 2

1

私は自分のアイデアの簡単なテストを行うためにいくつかのコードを書きました:

// Compile using gcc -O4 main.c && objdump -d a.out

void add4(float x0, float x1, float x2, float x3, 
          float y0, float y1, float y2, float y3, 
          float* out0, float* out1, float* out2, float* out3) {
  // Non-inlined version of this uses xmm registers and four separate
  // SIMD operations
    *out0 = x0 + y0;
    *out1 = x1 + y1;
    *out2 = x2 + y2;
    *out3 = x3 + y3;
}
void sub4(float x0, float x1, float x2, float x3,
          float y0, float y1, float y2, float y3,
          float* out0, float* out1, float* out2, float* out3) {
    *out0 = x0 - y0;
    *out1 = x1 - y1;
    *out2 = x2 - y2;
    *out3 = x3 - y3;
}
void add4_then_sub4(float x0, float x1, float x2, float x3,
          float y0, float y1, float y2, float y3,
          float z0, float z1, float z2, float z3,
          float* out0, float* out1, float* out2, float* out3) {
    // In non-inlined version of this, add4 and sub4 get inlined.
    // xmm regiesters get re-used for the add and subtract,
    // but there is still no 4-way SIMD
  float temp0,temp1,temp2,temp3;
  // temp= x + y
  add4(x0,x1,x2,x3,
       y0,y1,y2,y3,
       &temp0,&temp1,&temp2,&temp3);
  // out = temp - z
  sub4(temp0,temp1,temp2,temp3,
       z0,z1,z2,z3,
       out0,out1,out2,out3);
}
void add4_then_sub4_arrays(const float x[4],
                                const float y[4],
                                const float z[4],
                                float out[4])
{
    // This is a stand-in for the main function below, but since the arrays are aguments,
    // they can't be optimized out of the non-inlined version of this function.
    // THIS version DOES compile into (I think) a bunch of non-aligned moves,
    // and a single vectorized add a single vectorized subtract
    add4_then_sub4(x[0],x[1],x[2],x[3],
            y[0],y[1],y[2],y[3],
            z[0],z[1],z[2],z[3],
            &out[0],&out[1],&out[2],&out[3]
            );
}

int main(int argc, char **argv) 
{
}

add4_then_sub4_arrays 用に生成されたアセンブリを考えてみましょう。

0000000000400600 <add4_then_sub4_arrays>:
  400600:       0f 57 c0                xorps  %xmm0,%xmm0
  400603:       0f 57 c9                xorps  %xmm1,%xmm1
  400606:       0f 12 06                movlps (%rsi),%xmm0
  400609:       0f 12 0f                movlps (%rdi),%xmm1
  40060c:       0f 16 46 08             movhps 0x8(%rsi),%xmm0
  400610:       0f 16 4f 08             movhps 0x8(%rdi),%xmm1
  400614:       0f 58 c1                addps  %xmm1,%xmm0
  400617:       0f 57 c9                xorps  %xmm1,%xmm1
  40061a:       0f 12 0a                movlps (%rdx),%xmm1
  40061d:       0f 16 4a 08             movhps 0x8(%rdx),%xmm1
  400621:       0f 5c c1                subps  %xmm1,%xmm0
  400624:       0f 13 01                movlps %xmm0,(%rcx)
  400627:       0f 17 41 08             movhps %xmm0,0x8(%rcx)
  40062b:       c3                      retq   
  40062c:       0f 1f 40 00             nopl   0x0(%rax)

配列が整列されていないため、理想よりもはるかに多くの移動操作があり、その xor がそこで何をしているのかわかりませんが、実際には、必要に応じて 1 つの 4 方向の加算と 1 つの 4 方向の減算があります。

その答えは、gcc には、スカラー浮動小数点演算を SIMD 演算にパックする機能が少なくとも ~ あるということです。

更新: 両方を使用したよりタイトなコードgcc-4.8 -O3 -march=native main.c && objdump -d a.out:

0000000000400600 <add4_then_sub4_arrays>:
  400600:       c5 f8 10 0e             vmovups (%rsi),%xmm1
  400604:       c5 f8 10 07             vmovups (%rdi),%xmm0
  400608:       c5 f0 58 c0             vaddps %xmm0,%xmm1,%xmm0
  40060c:       c5 f8 10 0a             vmovups (%rdx),%xmm1
  400610:       c5 f8 5c c1             vsubps %xmm1,%xmm0,%xmm0
  400614:       c5 f8 11 01             vmovups %xmm0,(%rcx)
  400618:       c3                      retq   
  400619:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

clang-4.0 -O3 -march=native main.c && llvm-objdump -d a.out:

add4_then_sub4_arrays:
  4005e0:       c5 f8 10 07                                     vmovups (%rdi), %xmm0
  4005e4:       c5 f8 58 06                                     vaddps  (%rsi), %xmm0, %xmm0
  4005e8:       c5 f8 5c 02                                     vsubps  (%rdx), %xmm0, %xmm0
  4005ec:       c5 f8 11 01                                     vmovups %xmm0, (%rcx)
  4005f0:       c3                                              ret
  4005f1:       66 66 66 66 66 66 2e 0f 1f 84 00 00 00 00 00    nopw    %cs:(%rax,%rax)
于 2016-12-06T14:39:53.900 に答える
-1

あなたの懸念は正しいです。これら 4 つの加算を自動ベクトル化するコンパイラはありません。入力が連続して配置されていないことを考えると、それだけの価値はありません。引数を SIMD レジスターに集めるコストは、ベクトル加算の節約よりもはるかに高くなります。

もちろん、コンパイラがアラインされたストリーミング ロードを使用できない理由は、引数をスカラーとして渡したからです。

于 2016-12-06T12:58:15.153 に答える