Cortex-A8 の NEON のように SSE も試してみました。
やりかたは前回と全く同じように、ひたすら命令を並べてスループットの測定。
今回はスレッドにも対応しておきます。
static void Thread_SSE_MULPS( Thread::Sync* barrier ) { __asm { xorps xmm0, xmm0 ~ }; barrier->Barrier( THREAD_COUNT ); for( int i= 0 ; i< VECTOR_LOOP ; i++ ){ __asm { mulps xmm0, xmm1 mulps xmm1, xmm2 mulps xmm2, xmm3 mulps xmm3, xmm4 mulps xmm4, xmm5 mulps xmm5, xmm6 mulps xmm6, xmm7 mulps xmm7, xmm0 ~ }; } barrier->Barrier( THREAD_COUNT ); } void Start_SSE( void (*func)( Thread::Sync* ), const char* msg ) { MultiThreadClassthread; Thread::Sync barrier; barrier.Init(); thread.Start( func, &barrier ); TimerClass timer; timer.Begin(); func( &barrier ); timer.End( msg ); barrier.Quit(); thread.Join(); } void SSE_main() { Start_SSE( Thread_SSE_MULPS, "SSE_MULPS" ); }
上記のようにメモリアクセスの無い命令を前回同様に 40 個 (8×5) 並べます。
スレッドの開始と終了待ちはシグナルで同期します。一番遅いスレッドの終了に合わせているため、
速く終了したスレッドがあると若干無駄が生じる可能性があります。
メインスレッドが実行している間に、サブスレッドで何回命令を実行出来たか数えた方が
より正確な結果になるかもしれません。
Core i7 920 2.67GHz を使っています。1 スレッドあたり 1G 回ループします。
SSE_MULPS 14.32 sec Thread x1
40命令 × 1G 回ループなので 40/14.32 = 2.79 。
CPU は 2.67GHz なので 1命令 / 1cycle に近い値だけど少々オーバーしています。
ターボブーストのおかげでしょうか。スレッドを増やしてみます。
SSE_MULPS 16.19 sec Thread x4 SSE_MULPS 29.05 sec Thread x8
core 数 (4) までは大きく増えませんが、8スレッド時は、1スレッドのほぼ 2倍の時間が
かかっています。HT を含めるとハードウエアで 8スレッドですが、パイプラインに空きが
ないためか HT の効果が全く無いことがわかります。
8スレッド時の値を元に計算してみると Core i7 920 2.67GHz は mulps で
40命令 × 1G回ループ × 4 SIMD × 8 Thread / 29.05 秒 = およそ 44.06 G FLOPS
SSE には積和がありませんが SSE4.1 には DPPS があることを思い出しました。
Mul x4 + Add x3 なので試してみます。
__asm { dpps xmm0, xmm1, 255 dpps xmm1, xmm2, 255 dpps xmm2, xmm3, 255 ~
SSE_DPPS 28.74 sec Thread x1 SSE_DPPS 31.02 sec Thread x4 SSE_DPPS 57.73 sec Thread x8
mul のちょうど 2倍、スループット 2 で実行しているようです。
2 cycle で 7 float なので演算量自体は mulps に劣ります。
最初の乗算を敢えてストールするように組み替えてみます。
__asm { mulps xmm0, xmm0 mulps xmm0, xmm0 mulps xmm0, xmm0 ~
SSE_MULPS_ST 57.36 sec Thread x1 SSE_MULPS_ST 57.51 sec Thread x4 SSE_MULPS_ST 58.63 sec Thread x8
インターリーブ時は 14.32秒だったので、4倍の実行時間がかかるようになりました。
mulps のレイテンシが 4 cycle あることがわかります。パイプラインはがら空きで、
4スレッド時と 8スレッド時の実行速度に差がありません。HT がめいっぱい働いており、
それでもまだ半分ほど無駄が生じていることになります。
SSE_DPPS_ST 157.57 sec Thread x1 SSE_DPPS_ST 157.68 sec Thread x4 SSE_DPPS_ST 157.74 sec Thread x8
同じように dpps もストールさせてみると、こちらはさらに時間がかかります。
5.5 倍かかっているためスループット 2 、レイテンシ 11 で実行しているようです。
Atom でも試してみました。遅いのでループ回数を 1/10 (100M) に減らしています。
Atom 1.8GHz SSE_MULPS 4.36 sec Thread x1 Atom 1.8GHz SSE_MULPS 8.70 sec Thread x2 Atom 1.8GHz SSE_ADDPS 2.25 sec Thread x1 Atom 1.8GHz SSE_ADDPS 4.46 sec Thread x1 Atom 800MHz SSE_MULPS 10.15 sec Thread x1 Atom 800MHz SSE_MULPS 20.29 sec Thread x2
乗算は 2 float/cycle、加算で 4 float/cycle 実行しています。
やはりパイプラインを埋めると HT の効果が無いことがわかります。
Atom 1.8GHz SSE_MULPS_ST 10.88 sec Thread x1 Atom 1.8GHz SSE_MULPS_ST 10.92 sec Thread x2 Atom 1.8GHz SSE_MULPS_ST 21.92 sec Thread x4
乗算はストール発生時 2.5 倍遅くなっています。スループット 2/ レイテンシ 5
結果として Atom も Cortex-A8 + NEON も、1 cycle あたり 2乗算 or 4加算で同等です で同等、加算は Atom の方が高速です。
ただし積和の場合は Atom は 3cycle かかるところ NEON は 2cycle で済むため、
この組み合わせでは Cortex-A8 + NEON の方が速いことになります。
浮動小数演算も同クロック数の Atom より高速に実行できそうです。
2009/10/02 追記>: 加算が多いと Atom の方が速く、最適化が弱い場合は HT が有効な分 Atom が有利なこともあります。
もちろん他の命令の実行速度にもよるし、メモリアクセスや演算以外の転送命令等も絡んで
くるので一概には言えません。レイテンシも大きめなので最適化をどれだけ出来るかに
かかってきます。
厳密なデータではありませんが、以前の結果を見ても浮動小数演算以外は同サイクルの
Atom より速い速度で実行できているし、浮動小数演算も潜在能力は高そうです。
問題は VFP でしょうか。
関連エントリ
・NetWalker PC-Z1 Cortex-A8 浮動小数演算の実行速度
・NetWalker PC-Z1 Atom と速度比較