別のベクトルの周りのベクトルを所定の角度に回転させるコードがあります。私はクォータニオンを使用し、これを行うにはfast formulaです。私は、SIMDコンパイラ組み込み関数を使用する場合と使用しない場合の2つの変種を記述しました。SSE SIMDコードでのパフォーマンスの問題
バリアント1:
#include <xmmintrin.h>
#include <pmmintrin.h>
#include "test2.h"
static __v4sf cross_product_ (__v4sf a, __v4sf b)
{
__v4sf r1 = a * _mm_shuffle_ps (b, b, _MM_SHUFFLE (1, 3, 2, 0));
__v4sf r2 = b * _mm_shuffle_ps (a, a, _MM_SHUFFLE (1, 3, 2, 0));
__v4sf r = r1 - r2;
return _mm_shuffle_ps (r, r, _MM_SHUFFLE (1, 3, 2, 0));
}
static __v4sf rotate_vector_ (__v4sf base, __v4sf vect)
{
__v4sf base_re = _mm_shuffle_ps (base, base, 0);
__v4sf tmp = cross_product_ (base, vect);
tmp = tmp * _mm_set_ps1 (2.0);
__v4sf res = vect + base_re*tmp + cross_product_ (base, tmp);
return res;
}
void rotate_vector (float base[], float vect[], float res[])
{
__v4sf v = _mm_slli_si128 (_mm_load_ps (vect), 4);
__v4sf r = rotate_vector_ (_mm_load_ps (base), v);
r = _mm_srli_si128 (r, 4);
_mm_store_ps (res, r);
}
バリアント2:
0......32......64......96......128 bits
1(real) i j k
とベクトルの:四元ため
#include "test2.h"
static void cross_product (const float v1[], const float v2[], float res[])
{
res[0] = v1[1]*v2[2] - v1[2]*v2[1];
res[1] = -v1[0]*v2[2] + v1[2]*v2[0];
res[2] = v1[0]*v2[1] - v1[1]*v2[0];
}
void rotate_vector (float base[], float vector[], float res[])
{
float tmp[3], tmp2[3];
int i;
cross_product (base+1, vector, tmp);
for (i=0; i<3; i++) tmp[i] *= 2.0;
cross_product (base+1, tmp, tmp2);
for (i=0; i<3; i++) res[i] = vector[i] + base[0]*tmp[i] + tmp2[i];
}
データレイアウト
0......32......64......96......128 bits
x y z XXX
次に、1回転四元数(軸xを中心に90度まで回転)でベクトルの初期化済み配列を回転しようとします。たくさんのRAMを使います!
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <strings.h>
#include "test2.h"
double gettime()
{
struct timeval tv;
gettimeofday (&tv, NULL);
return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
}
#define N 400000000
int main()
{
float z = sqrtf(2)/2;
float a[4] __attribute__((aligned(16))) = {z,z,0,0};
float (*b)[4] = aligned_alloc (16, 4*N*sizeof(float));
int i;
for (i=0; i<N; i++)
{
bzero (b[i], 16);
b[i][i%3] = 4;
b[i][0] = 1;
}
double time = gettime();
for (i=0; i<N; i++)
{
#if 0
b[i][0] = 1;
#endif
rotate_vector (a,b[i],b[i]);
}
time = gettime() - time;
printf ("%f %f %f\n", b[0][0], b[0][1], b[0][2]);
printf ("%f\n", time);
return 0;
}
SIMD変異打ち鳴らす3.4 -O3 -msse3
でコンパイル及びAMDのFX-6300プロセッサ上で実行されたとき、非SIMDよりも約10%高速です。しかし、#if/#endif
を取り除くと、言い換えると、各繰り返しで回転する必要のあるベクトルに何かを書き込むと、SIMDの変種が大きく減速し、非SIMDより約2〜2.5倍遅く実行されます。だから、どのように1つの書き込みが全体を遅くするのでしょうか?それはキャッシュとは何か?私はFreeBSD 10.2を使用していますが、このコードをpmcstat(8)でテストしようとしていますが、珍しいことは何も得られません(例えばキャッシュミス率が高いなど)。
パフォーマンスはAtomプロセッサ(Asus Zenfone 2 ze551mlスマートフォンとAcer Aspire One Happy 2ネットブックでテスト済み)の影響を受けていないようです。これはプロセッサ固有の問題でしょうか?または、私はSIMDを間違って理解していますが、これはそれらを適用するのに適切な場所ではありませんか?ここ
あなたはあなたのマシン上でこの例をコンパイルしたい場合は、(あなたがRAMの〜の6Gbが必要になります)test2.hが欠落しています。
#ifndef TEST2_H
#define TEST2_H
void quat_mul (float a[], float b[], float c[]);
void rotate_vector (float base[], float vect[], float res[]);
#endif
これは、おそらくより良い例ですが、より良い質問です。 (たとえそれがもはや意味のある計算ではなくても、あなたが見ている問題をまだ示している限り、SIMDコードを単純化するなど)。また、配列ループの外側に繰り返しループを持つことによって1トンのメモリを節約できました。メインメモリのボトルネックをテストする場合は、キャッシュサイズを超えるだけの大きさの配列が必要です。 –