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() 関数を使用し、「スマート」コンパイラに「最適化」させたことです。24ns/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 で、より優れていました。また、 Carmack の風変わりな Newton-Raphson 近似手法も試しました。これはハードウェアよりもさらに優れた 4.3ns/float で実行されましたが、誤差は 2 10分の 1 でした (これは私の目的には多すぎます)。
逆数平方根の 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— しかし、ここでの配列データ構造はテスト目的のみです: 私が実際に測定しようとしているのは、コードで使用するためのスカラーパフォーマンスですこれはベクトル化できません)。