[解決済み] なぜSSEスカラーsqrt(x)はrsqrt(x) * xより遅いのですか?
質問
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つの浮動小数点演算を処理します。
関連
-
[解決済み] B "の印刷が "#"の印刷より劇的に遅いのはなぜですか?
-
[解決済み] Python 3で「1000000000000000 in range(1000000000000001)」はなぜ速いのですか?
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] なぜGCCはa*a*a*a*aを(a*a*a)*(a*a*a)に最適化しないのでしょうか?
-
[解決済み] <は<=より速いのか?
-
[解決済み] 0.1fを0にすると、なぜ10倍もパフォーマンスが落ちるのですか?
-
[解決済み] 通貨を表すのにDoubleやFloatを使ってはいけないのですか?
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] 1サイクルあたり4FLOPの理論上の最大値を達成するにはどうすればよいですか?
-
[解決済み] OFFSET / FETCH NEXTからの総行数取得
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン