Intをdoubleに、intをdoubleに、そしてもう一度(時には正当な理由で、時にはそうではない)変換するコードをよく目にしますが、これは私のプログラムでは「隠れた」コストのように思えます。変換方法が切り捨てであると仮定しましょう。
それで、それはどれくらい高いですか?ハードウェアによって異なると思いますので、新しいIntelプロセッサを想定しましょう(Haswell、よろしければ、何でも取りますが)。私が興味を持っているいくつかのメトリック(良い答えはそれらのすべてを持っている必要はありませんが):
また、実際に到着できるデータの量と比較して1秒あたりに実行できる計算の数の違いを考えると、遅い変換の影響を最も深刻に経験する方法は、実行速度ではなく電力使用量に関するものだと思います。毎秒CPUで。
X86-64でFP SSE2を使用した計算(C++の切り捨てセマンティクスの丸めモードの変更に費用がかかるレガシーx87ではない)を実行するために、私が自分で掘り下げることができるものは次のとおりです。
私が 生成されたアセンブリを見てください clangとgccから、キャストint
からdouble
のように見えますが、要約すると1つの命令になります:cvttsd2si
。
double
からint
まではcvtsi2sd
です。 (cvtsi2sdl
32ビットオペランドサイズのcvtsi2sd
のAT&T構文。)
自動ベクトル化を使用すると、cvtdq2pd
が得られます。
だから私は質問が次のようになると思います:それらのコストは何ですか?
これらの命令はそれぞれ、FP addsd
+ movq xmm, r64
(fp <-integer)またはmovq r64, xmm
(integer <-fp)とほぼ同じコストです。 、メインストリーム(Sandybridge/Haswell/Sklake)IntelCPUで同じポート上にある2uopsにデコードするため。
Intel®64およびIA-32アーキテクチャ最適化リファレンスマニュアル は、cvttsd2si
命令のコストは5レイテンシであると述べています(付録C-16を参照)。 cvtsi2sd
は、アーキテクチャに応じて、Silvermontの1から他のいくつかのアーキテクチャの7-16のようなレイテンシまで変化します。
Agner Fogの命令テーブル シルバーモントのcvtsi2sd
の5サイクルレイテンシ(2クロックスループットに1)やHaswellの4cレイテンシ(クロックに1)など、より正確でわかりやすい数値がありますスループット(gccが通常pxor xmm0,xmm0
で行うように、宛先レジスタへの依存が古い上半分とマージされないようにする場合)。
SIMDパック-float
からパック-int
は素晴らしいです。単一のuop。ただし、double
に変換するには、要素サイズを変更するためにシャッフルする必要があります。 SIMD float/double <-> int64_tはAVX512まで存在しませんが、限られた範囲で手動で実行できます。
Intelのマニュアルでは、レイテンシを次のように定義しています。「実行コアが命令を形成するすべてのμopsの実行を完了するために必要なクロックサイクル数」。ただし、より有用な定義は、入力の準備ができてから出力の準備ができるまでのクロック数です。アウトオブオーダー実行がその仕事をするのに十分な並列処理がある場合、スループットはレイテンシーよりも重要です: 最新のスーパースカラープロセッサでの操作のレイテンシーを予測する際にどのような考慮事項があり、それらを手動で計算するにはどうすればよいですか? 。
同じIntelのマニュアルによると、整数add
命令のレイテンシは1で、整数imul
のコストは3です(付録C-27)。 FP addsd
およびmulsd
は、Skylakeで、クロックスループットあたり2で、4サイクルの遅延で実行されます。SIMDバージョンとFMAで同じで、128です。または256ビットのベクトル。
Haswellでは、addsd
/addpd
はクロックスループットごとに1つだけですが、専用のFP追加ユニットのおかげで3サイクルのレイテンシがあります。
したがって、答えは次のように要約されます。
1)ハードウェアが最適化されており、コンパイラーはハードウェア機構を活用します。
2)一方向のサイクル数に関しては、乗算よりも少しだけコストがかかり、他の方向では非常に変動します(アーキテクチャによって異なります)。そのコストは無料でもばかげたことでもありませんが、自明ではない方法でコストが発生するコードを書くのがいかに簡単であるかを考えると、おそらくもっと注意を払う必要があります。
もちろん、この種の質問は、正確なハードウェア、さらにはモードによっても異なります。
オン x86 my i7 2ビットモードで使用する場合デフォルトオプション(_gcc -m32 -O3
_)を使用すると、int
からdouble
への変換は非常に高速ですが、その逆はC標準では、ばかげたルール(小数の切り捨て)が義務付けられているため、はるかに遅くなります。
この丸め方法は、数学とハードウェアの両方に悪影響を及ぼし、FPUがこの特別な丸めモードに切り替えて切り捨てを実行し、正常な丸め方法に戻す必要があります。
単純なfistp
命令を使用してfloat-> int変換を実行する速度が必要な場合は、計算結果がより高速ではるかに優れていますが、インラインアセンブリが必要です。
_inline int my_int(double x)
{
int r;
asm ("fldl %1\n"
"fistpl %0\n"
:"=m"(r)
:"m"(x));
return r;
}
_
ナイーブなx = (int)y;
変換よりも6倍以上高速です(0へのバイアスはありません)。
ただし、64ビットモードで使用した場合、まったく同じプロセッサには速度の問題はなく、fistp
コードを使用すると、実際にはコードの実行速度が多少遅くなります。
どうやら、ハードウェアの人たちはあきらめて、ハードウェアに直接悪い丸めアルゴリズムを実装しました(したがって、悪い丸めコードは今では速く実行できます)。