以下のような構造のコードがあります。多数の小さな 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];
}