x86 の場合、ハードウェア FP 平方根命令は高速です ( sqrtss
is sqrt Scalar Single-precision)。単精度は倍精度よりも高速であるため、より低い精度を使用できるコードでは、float
代わりに必ず使用してください。double
32 ビット コードの場合、通常、x87 ではなく SSE 命令を使用して FP 演算を行うには、コンパイラ オプションが必要です。(例-mfpmath=sse
)
C のsqrt()
または関数を単にorsqrtf()
としてインライン化するには、でコンパイルする必要があります。NaN に数学関数を設定することは、一般に設計ミスと見なされますが、標準ではそれが必要です。そのオプションがないと、gcc はそれをインライン化しますが、結果が NaN かどうかを確認するために比較+ブランチを実行し、そうであればライブラリ関数を呼び出して を設定できるようにします。プログラムが数学関数の後にチェックしない場合、 を使用しても危険はありません。sqrtsd
sqrtss
-fno-math-errno
errno
errno
errno
-fno-math-errno
-ffast-math
getの「安全でない」部分sqrt
や他の関数をインライン化する必要はありませんが-ffast-math
、大きな違いを生む可能性があります (たとえば、結果が変わる場合にコンパイラが自動ベクトル化できるようにするなど)。FP 演算は連想的ではありません。
たとえば、gcc6.3 コンパイルでfloat foo(float a){ return sqrtf(a); }
foo: # with -O3 -fno-math-errno.
sqrtss xmm0, xmm0
ret
foo: # with just -O3
pxor xmm2, xmm2 # clang just checks for NaN, instead of comparing against zero.
sqrtss xmm1, xmm0
ucomiss xmm2, xmm0
ja .L8 # take the slow path if 0.0 > a
movaps xmm0, xmm1
ret
.L8: # errno-setting path
sub rsp, 24
movss DWORD PTR [rsp+12], xmm1 # store the sqrtss result because the x86-64 SysV ABI has no call-preserved xmm regs.
call sqrtf # call sqrtf just to set errno
movss xmm1, DWORD PTR [rsp+12]
add rsp, 24
movaps xmm0, xmm1 # extra mov because gcc reloaded into the wrong register.
ret
NaN の場合の gcc のコードは複雑すぎるようです。sqrtf
戻り値さえ使用しません! -fno-math-errno
とにかく、これは、プログラム内のすべてのsqrtf()
呼び出しサイトで、がなくても実際に発生する種類の混乱です。ほとんどの場合、コードの肥大化であり、.L8
0.0 以上の数値の sqrt を取得するときにブロックは実行されませんが、高速パスにはまだいくつかの余分な命令があります。
sqrt
への入力がゼロでないことがわかっている場合rsqrtps
は、高速ですが非常に近似した逆数 sqrt 命令(またはrsqrtss
スカラー バージョン) を使用できます。1 回のニュートン ラフソン反復により、ハードウェアの単精度sqrt
命令とほぼ同じ精度になりますが、完全ではありません。
sqrt(x) = x * 1/sqrt(x)
、 forx!=0
であるため、rsqrt と乗算を使用して sqrt を計算できます。これらは両方とも、P4 でも高速です (2013 年にはまだ関係がありましたか)?
P4 では、それで何かを割る必要がなくても、rsqrt
+ ニュートン反復を使用して単一の を置き換える価値があるかもしれません。sqrt
ニュートン反復でとして計算するときにゼロを処理することについて最近書いた回答sqrt(x)
x*rsqrt(x)
も参照してください。FP 値を整数に変換する場合の丸め誤差の説明と、他の関連する質問へのリンクを含めました。
P4:
スカイレイク:
sqrtss
/ sqrtps
: 12c レイテンシ、3c スループットあたり 1 つ
sqrtsd
/ sqrtpd
: 15 ~ 16c のレイテンシ、4 ~ 6c のスループットごとに 1 つ
fsqrt
(x87): 14 ~ 21cc のレイテンシ、4 ~ 7c のスループットごとに 1 つ
rsqrtss
/ mulss
: 4c + 4c レイテンシ。1c スループットあたり 1 つ。
- SIMD 128b ベクター バージョンは同じ速度です。256b ベクトル バージョンはレイテンシが少し高く、スループットはほぼ半分です。この
rsqrtss
バージョンは、256b ベクトルに対して完全なパフォーマンスを発揮します。
Newton Iteration を使用すると、rsqrt
バージョンは速くなったとしてもそれほど速くはありません。
Agner Fog の実験的テストからの数値。彼のマイクロアーチ ガイドを参照して、コードの実行速度が速くなったり遅くなったりする原因を理解してください。x86タグ wikiのリンクも参照してください。
IDK の sin/cos の最適な計算方法。fsin
ハードウェア/ fcos
(および両方を同時に実行するわずかに遅いfsincos
もの)は最速の方法ではなく、IDKが何であるかを読みました。