web-dev-qa-db-ja.com

浮動小数点の加算と浮動小数点の乗算の相対速度はどれくらいですか

10年か2年前は、乗算と除算の使用を避け、代わりに加算と減算を使用するために数値コードを書くことは価値がありました。良い例は、多項式を直接計算する代わりに、 前方差分 を使用して多項式曲線を評価することです。

これはまだ当てはまりますか、それとも*、/が+、-よりも何倍も遅くならないところまで最新のコンピュータアーキテクチャが進んでいますか?

具体的には、ソフトウェアでFP)を実行しようとする小さなマイクロではなく、広範なオンボード浮動小数点ハードウェアを備えた最新の一般的なx86チップで実行されるコンパイル済みC/C++コードに興味があります。パイプライン処理やその他のアーキテクチャの強化により、特定のサイクルカウントが排除されることを認識していますが、それでも有用な直感を得たいと思います。

28
J. Peterson

また、命令の組み合わせにも依存します。プロセッサにはいつでも複数の計算ユニットが待機しており、それらすべてが常に満たされている場合に最大のスループットが得られます。したがって、mulのループの実行は、ループまたは加算の実行と同じくらい高速ですが、式がより複雑になった場合、同じことは当てはまりません。

たとえば、次のループを考えてみましょう。

for(int j=0;j<NUMITER;j++) {
  for(int i=1;i<NUMEL;i++) {
    bla += 2.1 + arr1[i] + arr2[i] + arr3[i] + arr4[i] ;
  }
}

nUMITER = 10 ^ 7、NUMEL = 10 ^ 2の場合、両方の配列が小さな正の数に初期化されます(NaNははるかに低速です)。これには、64ビットプロシージャでdoubleを使用すると6.0秒かかります。ループを次のように置き換えると

bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;

たった1.7秒しかかかりません...したがって、追加を「やり過ぎた」ので、mulsは本質的に無料でした。そして追加の削減が助けになりました。それはもっと混乱します:

bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;

-同じmul/add分布ですが、定数が乗算されるのではなく加算されるようになりました-3.7秒かかります。お使いのプロセッサは、一般的な数値計算をより効率的に実行するように最適化されている可能性があります。したがって、マルの合計やスケーリングされた合計のような内積は、ほぼ同じくらい優れています。定数の追加はそれほど一般的ではないので、それは遅くなります...

bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/

再び1.7秒かかります。

bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/

(最初のループと同じですが、高価な定数の追加なし:2.1秒)

bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/

(ほとんどはmulsですが、1つ追加:1.9秒)

だから基本的に;どちらが速いかはわかりませんが、ボトルネックを回避したい場合は、適切な混合を行い、NaNまたはINFを回避し、定数の追加を回避することがより重要です。多くの場合、小さな変更で違いが生じる可能性があるため、何をするにしても、必ずさまざまなコンパイラ設定をテストしてテストしてください。

さらにいくつかのケース:

bla *= someval; // someval very near 1.0; takes 2.1 seconds
bla *= arr1[i] ;// arr1[i] all very near 1.0; takes 66(!) seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; // 1.6 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, 2.2 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, floats 2.2 seconds
bla += someval * arr1[i]* arr2[i];// 0.9 in x64, 1.6 in x86
bla += someval * arr1[i];// 0.55 in x64, 0.8 in x86
bla += arr1[i] * arr2[i];// 0.8 in x64, 0.8 in x86, 0.95 in CLR+x64, 0.8 in CLR+x86
22
Eamon Nerbonne

理論的には情報はここにあります:

インテル®64およびIA-32アーキテクチャー最適化リファレンス・マニュアル、付録C命令の待ち時間とスループット

彼らがリストしているすべてのプロセッサについて、FMULのレイテンシーはFADDまたはFDIVのレイテンシーに非常に近いです。一部の古いプロセッサでは、FDIVはそれより2〜3倍遅くなりますが、新しいプロセッサではFMULと同じです。

警告:

  1. 私がリンクしたドキュメントには、実際にはこれらの数値に頼ることはできないと書かれています。これは、プロセッサが正しい場合に処理を高速化するためです。

  2. コンパイラーが、浮動小数点の乗算/除算を使用できる多くの新しい命令セットの1つを使用することを決定する可能性は十分にあります。

  3. これは、コンパイラの作成者だけが読むことを目的とした複雑なドキュメントであり、間違っている可能性があります。一部のCPUでFDIVレイテンシーの数値が完全に欠落している理由がわかりません。

18
Scott McIntyre

この質問に答える最良の方法は、実行する必要のある処理のベンチマーク/プロファイルを実際に作成することです。可能な限り、理論よりも経験を使用する必要があります。特に達成しやすい場合。

実行する必要のある数学のさまざまな実装をすでに知っている場合は、数学のいくつかの異なるコード転送を記述して、パフォーマンスがピークになる場所を確認できます。これにより、プロセッサ/コンパイラがさまざまな実行ストリームを生成してプロセッサパイプラインを埋め、その答えに対する具体的な答えを得ることができます。

特にDIV/MUL/ADD/SUBタイプの命令のパフォーマンスに関心がある場合は、インラインアセンブリをトスして、これらの命令のどのバリアントが実行されるかを具体的に制御することもできます。ただし、システムのパフォーマンスを把握するには、複数の実行ユニットをビジー状態に保つ必要があります。

また、このようなことを行うと、同じプログラムを実行するだけで、プロセッサの複数のバリエーションのパフォーマンスを比較でき、マザーボードの違いを考慮に入れることもできます。

編集:

+-の基本的なアーキテクチャは同じです。したがって、論理的には計算に同じ時間がかかります。 *一方、単一の操作を完了するには、通常「全加算器」で構成される複数の層が必要です。これは、サイクルごとに*をパイプラインに発行できますが、加算/減算回路よりもレイテンシーが高くなることを保証します。 fp /演算は通常、時間の経過とともに正解に向かって繰り返し収束する近似法を使用して実装されます。これらのタイプの近似は、通常、乗算によって実装されます。したがって、浮動小数点の場合、乗算(すでに大きな回路であり、それ自体である)を多数の乗算回路のパイプラインに「展開」することは非現実的であるため、一般に除算に時間がかかると想定できます。それでも、特定のシステムのパフォーマンスは、テストによって最もよく測定されます。

7
NoMoreZealots

明確な参照を見つけることはできませんが、広範な実験により、最近の浮動小数点の乗算は加算および減算とほぼ同じ速度であり、除算はそうで​​はありません(ただし、「何度も」遅くなることもありません)。独自の実験を実行するだけで、必要な直感を得ることができます。事前に乱数(数百万)を生成し、タイミングを開始する前にそれらを読み取り、CPUのパフォーマンスカウンターを使用することを忘れないでください(他のプロセスを実行せずに、正確な測定のために)からそれらを止めることができる限り!

1
Alex Martelli

*/vs +-の速度差は、プロセッサアーキテクチャによって異なります。一般に、特にx86では、最近のプロセッサでは速度の差が小さくなっています。 *疑わしい場合は、+に近いはずです。実験してください。多くのFP操作で非常に難しい問題がある場合は、ベクトルプロセッサとして機能するGPU(GeForce、...)の使用も検討してください。

1
Marc