私はIntel Core Duoでいくつかのコア計算をプロファイリングしており、平方根へのさまざまなアプローチを検討しているときに、奇妙なことに気付きました:SSEスカラー演算を使用すると、逆平方根を取得して乗算する方が高速ですネイティブのsqrtオペコードを使用するよりも、sqrtを取得する方が便利です。
私はそれを次のようなループでテストしています:
inline float TestSqrtFunction( float in );
void TestFunc()
{
#define ARRAYSIZE 4096
#define NUMITERS 16386
float flIn[ ARRAYSIZE ]; // filled with random numbers ( 0 .. 2^22 )
float flOut [ ARRAYSIZE ]; // filled with 0 to force fetch into L1 cache
cyclecounter.Start();
for ( int i = 0 ; i < NUMITERS ; ++i )
for ( int j = 0 ; j < ARRAYSIZE ; ++j )
{
flOut[j] = TestSqrtFunction( flIn[j] );
// unrolling this loop makes no difference -- I tested it.
}
cyclecounter.Stop();
printf( "%d loops over %d floats took %.3f milliseconds",
NUMITERS, ARRAYSIZE, cyclecounter.Milliseconds() );
}
TestSqrtFunctionのいくつかの異なるボディでこれを試してみましたが、本当に頭を悩ましているいくつかのタイミングがあります。最悪なのは、ネイティブのsqrt()関数を使用して、「スマート」コンパイラーを「最適化」することです。24 ns / floatで、x87 FPUを使用すると、これは明らかに悪いことでした。
inline float TestSqrtFunction( float in )
{ return sqrt(in); }
次に試したのは、コンパイラにSSEのスカラーsqrtオペコードを使用させるために組み込み関数を使用することでした。
inline void SSESqrt( float * restrict pOut, float * restrict pIn )
{
_mm_store_ss( pOut, _mm_sqrt_ss( _mm_load_ss( pIn ) ) );
// compiles to movss, sqrtss, movss
}
これは11.9ns / floatでより優れていました。また、カーマックの奇抜なニュートンラフソン近似手法も試してみました。これは、ハードウェアよりも4.3ns / floatで実行されましたが、エラーは1 in 2 10でした(これは、私の目的には多すぎます)。
危険なのは、逆平方根のSSE演算を試し、乗算を使用して平方根を取得したときです(x * 1 /√x=√x)。これには2つの依存する演算が必要ですが、1.24ns / floatで2 -14までの精度で、これははるかに高速なソリューションでした。
inline void SSESqrt_Recip_Times_X( float * restrict pOut, float * restrict pIn )
{
__m128 in = _mm_load_ss( pIn );
_mm_store_ss( pOut, _mm_mul_ss( in, _mm_rsqrt_ss( in ) ) );
// compiles to movss, movaps, rsqrtss, mulss, movss
}
私の質問は基本的に何が与えるのですか?SSEの組み込みからハードウェアへの平方根オペコードが他の2つの数学演算から合成するよりも遅いのはなぜですか?
私は確認したので、これは実際にはオペレーション自体のコストであると確信しています:
- すべてのデータはキャッシュに収まり、アクセスはシーケンシャルです
- 関数はインライン化されます
- ループを展開しても違いはありません
- コンパイラー・フラグは完全最適化に設定されています(そして、アセンブリーは良好です。
(編集:stephentyroneは、数値の長い文字列の操作では、ベクトル化SIMDパック演算を使用する必要があることを正しく指摘していますrsqrtps
が、ここでの配列データ構造は、テスト目的のみです。私が実際に測定しようとしているのは、コードで使用するスカラーパフォーマンスです。ベクトル化できません。)
inline float SSESqrt( float restrict fIn ) { float fOut; _mm_store_ss( &fOut, _mm_sqrt_ss( _mm_load_ss( &fIn ) ) ); return fOut; }
。しかし、これは悪い考えです。CPUがフロートをスタックに書き込み、すぐにそれらを読み戻すと、ロードヒットストアのストールを簡単に引き起こす可能性があるためです。特に、戻り値のベクトルレジスタからフロートレジスタにジャグリング悪い知らせです。さらに、SSEコンパイラ組み込み関数が表す基になるマシンオペコードは、とにかくアドレスオペランドを取ります。
eax
)は非常に悪いですが、xmm0とスタックの間の往復Intelのストアフォワーディングのため、戻り値は異なります。あなたは確かに自分でそれを見ることができます。一般に、潜在的なLHSを確認する最も簡単な方法は、発行されたアセンブリを調べて、レジスタセット間でデータがやり取りされる場所を確認することです。コンパイラが賢いこともあれば、そうでないこともあります。正規化ベクトルに関しては、私はここに私の結果を書いた:bit.ly/9W5zoU