1. ホーム
  2. performance

[解決済み] なぜSSEスカラーsqrt(x)はrsqrt(x) * xより遅いのですか?

2022-11-01 05:35:21

質問

Intel Core Duo 上でいくつかのコア数学をプロファイリングしており、平方根に対するさまざまなアプローチを見ているうちに、奇妙なことに気づきました:SSE スカラー演算を使用して、平方根の逆数を取り、それを乗じて平方根を得る方が、ネイティブの平方根演算コードを使用するより速いのです!

次のようなループでそれをテストしています。

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() 関数を使用して、コンパイラに "smart"optimize"をさせた場合です。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 の奇妙なニュートン・ラプソン近似法 を試してみましたが、これはハードウェアよりもさらに良い 4.3ns/float で動作しましたが、2 分の 1 の誤差がありました。 <ただし、誤差は 2 分の 1 です。 10 (これは私の目的には多すぎます) です。

に対する SSE op を試したときが、とんでもないことになりました。 逆数 平方根を得るために乗算を使用したときです (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 つの数学演算からそれを合成するよりも遅いのですか?

検証してみたので、本当にop自体のコストなんでしょうね。

  • すべてのデータはキャッシュに収まるので アクセスはシーケンシャルである
  • 関数はインライン化されています。
  • ループを展開しても違いはありません。
  • コンパイラのフラグが完全最適化に設定されている (そしてアセンブリは良好であることを確認した)

( 編集 : stephentyroneが正しく指摘しているように、長い数値列に対する演算は、以下のようなベクトル化SIMD packed opsを使用すべきです。 rsqrtps - のようなSIMD Packed Opsを使うべきであると指摘していますが、ここでの配列データ構造はあくまでテスト用です:私が本当に計測しようとしているのは スカラー のパフォーマンスです)。

どのように解決するのですか?

sqrtss は正しく丸められた結果を与えます。 rsqrtss 近似値 を約 11 ビットの精度で近似します。

sqrtss は、正確さが要求される場合に備えて、はるかに正確な結果を生成しています。 rsqrtss は、近似値で十分だが速度が要求される場合のために存在します。 Intel のドキュメントを読むと、ほぼ完全な精度 (私の記憶が正しければ、約 23 ビットの精度) を実現する命令シーケンス (平方根の逆近似と単一のニュートン ラプソン ステップ) も見つかりますが、その場合は sqrtss .

を編集します。 もし速度が重要で、本当に多くの値に対してループでこれを呼び出すのであれば、これらの命令のベクトル化されたバージョンを使うべきでしょう。 rsqrtps または sqrtps であり、どちらも1命令あたり4つの浮動小数点演算を処理します。