2016-03-26 9 views
1

別のベクトルの周りのベクトルを所定の角度に回転させるコードがあります。私はクォータニオンを使用し、これを行うには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 
+0

これは、おそらくより良い例ですが、より良い質問です。 (たとえそれがもはや意味のある計算ではなくても、あなたが見ている問題をまだ示している限り、SIMDコードを単純化するなど)。また、配列ループの外側に繰り返しループを持つことによって1トンのメモリを節約できました。メインメモリのボトルネックをテストする場合は、キャッシュサイズを超えるだけの大きさの配列が必要です。 –

答えて

4

は、右のベクトルロードを行う前に、単一の要素を書く原因となります店舗運送の停止。それがあなたのSIMDバージョンの性能を傷つけるかもしれません。あなたはパーフォーマンスカウンターを記録できるプロファイリングツールを使って確認できます。 Agner Fog's guidesおよびタグwikiの他のリンクを参照してください。

アトムのperfが影響を受けていないと言いました。それは私の理論を裏付ける強力な証拠です:Atomは驚くべき店舗運送能力を持ち、狭い店舗からそれに続く幅広い荷物にデータを転送することができます。他のすべてのx86マイクロアーキテクチャでは、ストア転送のストールが発生し、待ち時間がはるかに長くなります。 Agner Fogのmicroarch pdfがこれについて説明しています。


あなたは、単一のベクトル要素を変更したい場合は、それが_mm_insert_psでそれを行うには、おそらく最善です。あなたが多くを変更したい場合、それはprobです。 _mm_set_psを使用して新しいベクトルを作成し、次に_mm_blend_psを使用して古いベクトルと結合するのが最適です。 _mm_shuffle_psおよび_mm_unpacklo_ps/_mm_unpackhi_ps(またはpd)は、ベクトル間のデータを結合することもできます。

+1

ありがとう、非常に有用な答え。 _mm_set_ps()で新しいベクトルを作成して浮動小数点配列に要素を代入し、結果を回転して格納しました。その後、減速が消えた。 –

2

キャッシュにはまったく関係ないデータ(6.4 GB)があります。

すべての繰り返しで、メモリ内の1つのベクトル要素を変更し、ベクトルをload_psでロードし、いくつかの計算を行い、書き戻します。したがって、非ベクトル書き込みの後にベクトル書き込みがあります。最初の書き込みは、キャッシュラインを強制的にロードしてから、部分的にダーティになってから、ベクタとして読み込まれ、ベクタとして書き出されます。これはすべて複雑であり、プロセッサとメモリシステムの正確な設計によっては、速度が低下する可能性があります。

実際にb [i] [0]が使用されていた場合は、b [i] [0] = 1を呼び出した関数に割り当てる可能性が高くなります。したがって、ベクトルバージョンでは、ベクトルb [i]をload_psした後、ベクトルレジスタのベクトルの最初の要素を修正し、メモリの干渉を回避します。

+0

キャッシュラインが汚れているわけではありません。ベクトルロードと重複しないキャッシュラインの一部に書き込んだ場合、問題はありません。または、あなたがロードしようとしているアドレスに16Bストアを実行した場合は、ストア転送が機能します(IIRC、すべてのSSEをサポートするCPUが同じアドレスから16Bロードに16Bストアを転送できます)。 2番目の段落の唯一の部分は「複雑です」:P(L1にラインを入れるストアだが、それは問題ではない) –

関連する問題