質問
と前置きさせてください。私は ASM の経験が非常に限られており、SIMD の経験はさらに少ないです。
しかし、たまたま次の MMX/SSE に最適化されたコードがあるので、これを PPC/Cell プロセッサで使用するために AltiVec 命令に移植したいと考えています。
これはおそらく大きな質問です..ほんの数行のコードですが、ここで何が起こっているのかを理解するのに苦労は尽きませんでした。
元の関数:
static inline int convolve(const short *a, const short *b, int n)
{
int out = 0;
union {
__m64 m64;
int i32[2];
} tmp;
tmp.i32[0] = 0;
tmp.i32[1] = 0;
while (n >= 4) {
tmp.m64 = _mm_add_pi32(tmp.m64,
_mm_madd_pi16(*((__m64 *)a),
*((__m64 *)b)));
a += 4;
b += 4;
n -= 4;
}
out = tmp.i32[0] + tmp.i32[1];
_mm_empty();
while (n --)
out += (*(a++)) * (*(b++));
return out;
}
AltiVec 命令を使用するためにこれを書き直す方法についてヒントはありますか?
私の最初の試み (非常に間違った試み) は次のようになります。しかし、それは完全に(または少しでも)正しいわけではありません。
static inline int convolve_altivec(const short *a, const short *b, int n)
{
int out = 0;
union {
vector unsigned int m128;
int i64[2];
} tmp;
vector unsigned int zero = {0, 0, 0, 0};
tmp.i64[0] = 0;
tmp.i64[1] = 0;
while (n >= 8) {
tmp.m128 = vec_add(tmp.m128,
vec_msum(*((vector unsigned short *)a),
*((vector unsigned short *)b), zero));
a += 8;
b += 8;
n -= 8;
}
out = tmp.i64[0] + tmp.i64[1];
#endif
while (n --)
out += (*(a++)) * (*(b++));
return out;
}
解決
あなたはそれほど遠くありません - 私はいくつかの小さな問題を修正し、コードを少しクリーンアップし、テストハーネスを追加しました、そしてそれは今ではうまく機能しているようです:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <altivec.h>
static int convolve_ref(const short *a, const short *b, int n)
{
int out = 0;
int i;
for (i = 0; i < n; ++i)
{
out += a[i] * b[i];
}
return out;
}
static inline int convolve_altivec(const short *a, const short *b, int n)
{
int out = 0;
union {
vector signed int m128;
int i32[4];
} tmp;
const vector signed int zero = {0, 0, 0, 0};
assert(((unsigned long)a & 15) == 0);
assert(((unsigned long)b & 15) == 0);
tmp.m128 = zero;
while (n >= 8)
{
tmp.m128 = vec_msum(*((vector signed short *)a),
*((vector signed short *)b), tmp.m128);
a += 8;
b += 8;
n -= 8;
}
out = tmp.i32[0] + tmp.i32[1] + tmp.i32[2] + tmp.i32[3];
while (n --)
out += (*(a++)) * (*(b++));
return out;
}
int main(void)
{
const int n = 100;
vector signed short _a[n / 8 + 1];
vector signed short _b[n / 8 + 1];
short *a = (short *)_a;
short *b = (short *)_b;
int sum_ref, sum_test;
int i;
for (i = 0; i < n; ++i)
{
a[i] = rand();
b[i] = rand();
}
sum_ref = convolve_ref(a, b, n);
sum_test = convolve_altivec(a, b, n);
printf("sum_ref = %d\n", sum_ref);
printf("sum_test = %d\n", sum_test);
printf("%s\n", sum_ref == sum_test ? "PASS" : "FAIL");
return 0;
}
他のヒント
(警告:私の Altivec の経験はすべて Xbox360/PS3 での作業から来ています - 他の Altivec プラットフォームとどれだけ違うのかはわかりません)。
まず最初に、ポインタの位置を確認する必要があります。ほとんどのベクトル ロード (およびストア) 操作は、16 バイトで整列されたアドレスから行われることが想定されます。そうでない場合、通常は警告なしに処理が続行されますが、期待していたデータは取得できません。
非整列ロードを実行することも可能ですが (ただし速度は遅くなります)、基本的にはデータの前後を少し読み取って結合する必要があります。見る Apple の Altivec ページ. 。私も使用前にやりました lvlx
そして lvrx
命令をロードし、それらを OR 演算します。
次に、乗算と加算が同じかどうかわかりません。私は _mm_madd_pi16 も vec_msum も使用したことがないので、それらが同等であるとは断言できません。デバッガーでステップ実行し、同じ入力データに対して同じ出力が得られることを確認する必要があります。もう 1 つの考えられる違いは、オーバーフローを異なる方法で処理する可能性があることです (例:モジュラー vs.飽和します)。
最後になりましたが、一度に 2 つではなく 4 つの int を計算することになります。したがって、共用体は 4 つの int を保持し、最後に 4 つすべてを合計する必要があります。