webdevqa.jp.net

intとdoubleの間で変換するのにどれくらいの費用がかかりますか?

Intをdoubleに、intをdoubleに、そしてもう一度(時には正当な理由で、時にはそうではない)変換するコードをよく目にしますが、これは私のプログラムでは「隠れた」コストのように思えます。変換方法が切り捨てであると仮定しましょう。

それで、それはどれくらい高いですか?ハードウェアによって異なると思いますので、新しいIntelプロセッサを想定しましょう(Haswell、よろしければ、何でも取りますが)。私が興味を持っているいくつかのメトリック(良い答えはそれらのすべてを持っている必要はありませんが):

  1. 生成された命令の数
  2. 使用したサイクル数
  3. 基本的な算術演算と比較した相対コスト

また、実際に到着できるデータの量と比較して1秒あたりに実行できる計算の数の違いを考えると、遅い変換の影響を最も深刻に経験する方法は、実行速度ではなく電力使用量に関するものだと思います。毎秒CPUで。

21
Mark

X86-64でFP SSE2を使用した計算(C++の切り捨てセマンティクスの丸めモードの変更に費用がかかるレガシーx87ではない)を実行するために、私が自分で掘り下げることができるものは次のとおりです。

  1. 私が 生成されたアセンブリを見てください clangとgccから、キャストintからdoubleのように見えますが、要約すると1つの命令になります:cvttsd2si

    doubleからintまではcvtsi2sdです。 (cvtsi2sdl 32ビットオペランドサイズのcvtsi2sdのAT&T構文。)

    自動ベクトル化を使用すると、cvtdq2pdが得られます。

    だから私は質問が次のようになると思います:それらのコストは何ですか?

  2. これらの命令はそれぞれ、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の実行を完了するために必要なクロックサイクル数」。ただし、より有用な定義は、入力の準備ができてから出力の準備ができるまでのクロック数です。アウトオブオーダー実行がその仕事をするのに十分な並列処理がある場合、スループットはレイテンシーよりも重要です: 最新のスーパースカラープロセッサでの操作のレイテンシーを予測する際にどのような考慮事項があり、それらを手動で計算するにはどうすればよいですか?

  3. 同じ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)一方向​​のサイクル数に関しては、乗算よりも少しだけコストがかかり、他の方向では非常に変動します(アーキテクチャによって異なります)。そのコストは無料でもばかげたことでもありませんが、自明ではない方法でコストが発生するコードを書くのがいかに簡単であるかを考えると、おそらくもっと注意を払う必要があります。

31
Mark

もちろん、この種の質問は、正確なハードウェア、さらにはモードによっても異なります。

オン 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コードを使用すると、実際にはコードの実行速度が多少遅くなります。

どうやら、ハードウェアの人たちはあきらめて、ハードウェアに直接悪い丸めアルゴリズムを実装しました(したがって、悪い丸めコードは今では速く実行できます)。

5
6502