SSE組み込み関数の使用について少し読んで、doubleを使用してクォータニオンローテーションを実装して運試しをしました。以下は私が書いた通常のSSE関数です。
void quat_rot(quat_t a, REAL* restrict b){
///////////////////////////////////////////
// Multiply vector b by quaternion a //
///////////////////////////////////////////
REAL cross_temp[3],result[3];
cross_temp[0]=a.el[2]*b[2]-a.el[3]*b[1]+a.el[0]*b[0];
cross_temp[1]=a.el[3]*b[0]-a.el[1]*b[2]+a.el[0]*b[1];
cross_temp[2]=a.el[1]*b[1]-a.el[2]*b[0]+a.el[0]*b[2];
result[0]=b[0]+2.0*(a.el[2]*cross_temp[2]-a.el[3]*cross_temp[1]);
result[1]=b[1]+2.0*(a.el[3]*cross_temp[0]-a.el[1]*cross_temp[2]);
result[2]=b[2]+2.0*(a.el[1]*cross_temp[1]-a.el[2]*cross_temp[0]);
b[0]=result[0];
b[1]=result[1];
b[2]=result[2];
}
SSEを使用
inline void cross_p(__m128d *a, __m128d *b, __m128d *c){
const __m128d SIGN_NP = _mm_set_pd(0.0, -0.0);
__m128d l1 = _mm_mul_pd( _mm_unpacklo_pd(a[1], a[1]), b[0] );
__m128d l2 = _mm_mul_pd( _mm_unpacklo_pd(b[1], b[1]), a[0] );
__m128d m1 = _mm_sub_pd(l1, l2);
m1 = _mm_shuffle_pd(m1, m1, 1);
m1 = _mm_xor_pd(m1, SIGN_NP);
l1 = _mm_mul_pd( a[0], _mm_shuffle_pd(b[0], b[0], 1) );
__m128d m2 = _mm_sub_sd(l1, _mm_unpackhi_pd(l1, l1));
c[0] = m1;
c[1] = m2;
}
void quat_rotSSE(quat_t a, REAL* restrict b){
///////////////////////////////////////////
// Multiply vector b by quaternion a //
///////////////////////////////////////////
__m128d axb[2];
__m128d aa[2];
aa[0] = _mm_load_pd(a.el+1);
aa[1] = _mm_load_sd(a.el+3);
__m128d bb[2];
bb[0] = _mm_load_pd(b);
bb[1] = _mm_load_sd(b+2);
cross_p(aa, bb, axb);
__m128d w = _mm_set1_pd(a.el[0]);
axb[0] = _mm_add_pd(axb[0], _mm_mul_pd(w, bb[0]));
axb[1] = _mm_add_sd(axb[1], _mm_mul_sd(w, bb[1]));
cross_p(aa, axb, axb);
_mm_store_pd(b, _mm_add_pd(bb[0], _mm_add_pd(axb[0], axb[0])));
_mm_store_sd(b+2, _mm_add_pd(bb[1], _mm_add_sd(axb[1], axb[1])));
}
回転は基本的に関数を使用して行われます。
次に、次のテストを実行して、各関数が一連の回転を実行するのにかかる時間を確認します。
int main(int argc, char *argv[]){
REAL a[] __attribute__ ((aligned(16))) = {0.2, 1.3, 2.6};
quat_t q = {{0.1, 0.7, -0.3, -3.2}};
REAL sum = 0.0;
for(int i = 0; i < 4; i++) sum += q.el[i] * q.el[i];
sum = sqrt(sum);
for(int i = 0; i < 4; i++) q.el[i] /= sum;
int N = 1000000000;
for(int i = 0; i < N; i++){
quat_rotSSE(q, a);
}
printf("rot = ");
for(int i = 0; i < 3; i++) printf("%f, ", a[i]);
printf("\n");
return 0;
}
gcc4.6.3と-O3-std=c99-msse3を使用してコンパイルしました。
UNIXを使用した通常の機能のタイミングtime
は18.841秒で、SSEの場合は21.689秒でした。
何かが足りないのですが、SSEの実装が通常より15%遅いのはなぜですか?倍精度の場合、SSEの実装はどの場合に高速になりますか?
編集:コメントからアドバイスを受けて、私はいくつかのことを試みました、
- -O1オプションは非常によく似た結果をもたらします。
- 関数を使用
restrict
してみてcross_p
、2番目の外積を保持するために__m128dを追加しました。これは、製造されたアセンブリに違いはありませんでした。 - 私が理解していることから、正規関数用に生成されたアセンブリには、一部を除いてスカラー命令のみが含まれてい
movapd
ます。
SSE関数用に生成されたアセンブリコードは、通常のアセンブリコードよりわずか4行少なくなっています。
編集:生成されたアセンブリへのリンクを追加しました、