3

以下のような構造のコードがあります。多数の小さな SSE ヘルパー関数、ほとんどの作業を行う大きな関数、およびデータを整理し、大きな関数をループで実行し、残ったデータを処理するパブリック関数があります。

これにより、スカラー実装の約 2 倍の速度向上が得られましたが、可能であればそれ以上を取得したいと考えています。いくつかの概念的な問題と同様に、逆アセンブリにはいくつかの点がありました (x86 VC++ 2010 のみを詳細に調べましたが、x86 と GCC をサポートしています)。

少なくとも一部のターゲットでは、ここでは SSE と SSE2 しか使用できませんが、個別にビルドする価値がある場合は、新しい命令セットも使用できる可能性があります。


問題 1:

小さなヘルパーはすべて大きなヘルパーにうまくインライン化されましたが、大きなヘルパーはそうではありませんでした。

ただし、1 つのソース ファイル内の 1 つの関数によってのみ参照され、多数のレジスタが存在する場合でも (アルゴリズムを見ると、データ配列のロードを除いて、最大 12 個の XMM レジスタしか必要としないことは確かです)、コンパイラは次のことを望んでいるようです。 fooHelper の通常の呼び出し規則に従います。

したがって、データを foo の XMM レジスターに配置した後、それらをスタックに戻し、ポインターを渡します。次に、ループと片付けの後で、そのスタックを XMM にロードして、再度アンロードできるようにします...

強制的にfooHelperをインライン化できると思いますが、4つのXMMレジスタを使用してジョブを実行しないため、非常に多数の重複した命令になります。foo 自体で SSE を使用することもできませんでした。これにより、ロード/ストアの問題が解消されますが、fooHelper はこれら 4 つの状態変数で完全に不要なロードとストアを実行しています...

理想的には、これはプライベート関数であるため、通常の呼び出し規則を無視する方法があればよいでしょう。これは、すべてを完全にインライン化したくない SSE の他の多くの大きな部分で発生すると確信しています。


問題 2:

実装は基本的に、AAAA、BBBB、CCCC、DDDD として編成された 4 つの状態ベクトルで動作するため、4 つのデータ ストリームすべてを処理しながら、A、B、C、および D を個別の変数として動作するようにコードを簡単に記述できます。すぐに。

ただし、出力自体は ABCD、ABCD、ABCD、ABCD の形式であり、入力も 4 つの個別のバッファーであり、_m_set_epi32 をロードする必要があります。

これらの入力と出力 (実際にはフォーマットを変更できない) を処理するためのより良い方法はありますか?


namespace
{
    void fooHelperA(__m128i &a, __m128i b, __m128i x, int s)
    {
        ...small function (<5 sse operations)...
    }
    ...bunch of other small functions...

    //
    void fooHelper(        
         const int *data1, const int *data2, const int *data3, const int *data4,
         __m128i &a, __m128i &b, __m128i &c, __m128i &d)
    {
        //Get the current piece of data
        __m128 c = _mm_set_epi32(data1[0], data2[0], data3[0], data4[0]);
        ...do stuff with data...
        fooHelperA(a, b, c, 5);
        ...
        c = _mm_set_epi32(data1[1], data2[1], data3[1], data4[1]);
        ...
        fooHelperA(b, a, c, 7);
        ... lots more code ...
        c = _mm_set_epi32(data1[3], data2[3], data3[3], data4[3]);
        ...
    }
}
void foo(
    const char*data1, const char *data2, const float *data3, const char *data4,
    int*out1, int*out2, int*out3, int*out4,
    size_t len)
{
    __m128i a = _mm_setzero_si128();
    __m128i b = _mm_setzero_si128();
    __m128i c = _mm_setzero_si128();
    __m128i d = _mm_setzero_si128();
    while (len >= 16) //expected to loop <25 times for datasets in question
    {
        fooHelper((const int*)data1, (const int*)data2, (const int*)data3, (const int*)data4, a,b,c,d);
        data1 += 16;
        data2 += 16;
        data3 += 16;
        data4 += 16;
        len -= 16;
    }
    if (len)
    {
        int[4][4] buffer;
        ...padd data into buffer...
        fooHelper(buffer[0], buffer[1], buffer[2], buffer[3], a,b,c,d);
    }
    ALIGNED(16, int[4][4]) tmp;
    _mm_store_si128((__m128i*)tmp[0], a);
    _mm_store_si128((__m128i*)tmp[1], b);
    _mm_store_si128((__m128i*)tmp[2], c);
    _mm_store_si128((__m128i*)tmp[3], d);

    out1[0] = tmp[0][0];
    out2[0] = tmp[0][1];
    out3[0] = tmp[0][2];
    out4[0] = tmp[0][3];

    out1[1] = tmp[0][0];
    out2[1] = tmp[0][1];
    out3[1] = tmp[0][2];
    out4[1] = tmp[0][3];

    out1[2] = tmp[0][0];
    out2[2] = tmp[0][1];
    out3[2] = tmp[0][2];
    out4[2] = tmp[0][3];

    out1[3] = tmp[0][0];
    out2[3] = tmp[0][1];
    out3[3] = tmp[0][2];
    out4[3] = tmp[0][3];
}
4

1 に答える 1

2

いくつかのアドバイス、

1) コードとデータの説明を見ると、データ編成を SOA (配列の構造体) から、入力データが既に ABCD として編成されている構造体の AOS 配列に AAAA ベクトルに移動することで、大きな利益が得られるようです。 1 つの大きな入力ベクトル (4 倍大きい) を持ちます!

2)データの配置に注意してください。今のところ、 set_epi32 関数による pinalllity が必要かどうかは気にしませんが、AOS に切り替えると、高速ロード ( memory to XMS ) を使用できるはずです。

3) 関数の最後が少し奇妙です (今のところシミュレートできません) tmp 2d 配列が必要な理由がよくわかりません。

4) インターリーブ (および逆演算) は、SOA/AOS 変換の例を使用して実行できます... Intel は、SIMD 命令セットを推進する際に、このトピックに関する多くの論文を書きました。

がんばれ、アレックス

于 2013-06-27T20:19:36.507 に答える