46

gcc が実行可能ファイルで乗算をシフトに変換していることによく気づきました。anintと aを乗算すると、同様のことが発生する可能性がありfloatます。たとえば2 * f、 は単に の指数をf1 だけインクリメントして、いくつかのサイクルを節約できます。コンパイラは、おそらく (たとえば 経由で-ffast-math) 要求された場合、一般的に実行しますか?

scalb*()コンパイラは一般的にこれを行うのに十分スマートですか、またはldexp()/frexp()関数ファミリを使用して自分でこれを行う必要がありますか?

4

9 に答える 9

83

たとえば、2 * f は単純に f の指数を 1 だけインクリメントし、いくつかのサイクルを節約します。

これは単に真実ではありません。

まず、ゼロ、無限大、Nan、デノーマルなどのコーナー ケースが多すぎます。次に、パフォーマンスの問題があります。

誤解は、指数をインクリメントすることは乗算を行うことよりも速くないということです。

ハードウェアの指示を見ると、指数をインクリメントする直接的な方法はありません。したがって、代わりに行う必要があるのは次のとおりです。

  1. ビットごとに整数に変換します。
  2. 指数をインクリメントします。
  3. ビット単位で浮動小数点に変換します。

通常、整数実行ユニットと浮動小数点実行ユニットの間でデータを移動するには、中程度から大きなレイテンシがあります。結局、この「最適化」は、単純な浮動小数点乗算よりもはるかに悪いものになります。

したがって、コンパイラがこの「最適化」を行わない理由は、高速ではないためです。

于 2012-10-16T16:33:23.577 に答える
22

最新のCPU では、乗算は通常、サイクルごとに 1 つのスループットと低レイテンシーを備えています。値がすでに浮動小数点レジスタにある場合、表現に対して整数演算を行うためにそれをジャグリングすることでそれを打ち負かす方法はありません。そもそもメモリ内にあり、現在の値も正しい結果もゼロ、デノーマル、ナン、または無限大ではないことを想定している場合は、次のようなことを実行する方が速いかもしれません

addl $0x100000, 4(%eax)   # x86 asm example

2倍にする; これが有益であることがわかったのは、ゼロと無限大から離れた浮動小数点データの配列全体を操作していて、実行する操作が 2 の累乗によるスケーリングだけである場合だけです (したがって、データを浮動小数点レジスタにロードする既存の理由はありません)。

于 2012-10-16T17:49:50.017 に答える
20

一般的な浮動小数点形式、特に IEEE 754 では、指数が単純な整数として格納されないため、整数として扱うと正しい結果が得られません。

32 ビット float または 64 ビット double では、指数フィールドはそれぞれ 8 ビットまたは 11 ビットです。指数コード 1 ~ 254 (float の場合) または 1 ~ 2046 (double の場合) は整数のように機能します。これらの値の 1 つに 1 を加算し、結果がこれらの値の 1 つになる場合、表現される値は 2 倍になります。ただし、次の状況では追加に失敗します。

  • 初期値は 0 または非正規です。この場合、指数フィールドはゼロから始まり、それに 1 を追加すると、数値に 2 -126 (float の場合) または 2 -1022 (double の場合) が追加されます。数が 2 倍になることはありません。
  • 初期値が 2 127 (float の場合) または 2 1023 (double の場合) を超えています。この場合、指数フィールドは 254 または 2046 で始まり、それに 1 を追加すると数値が NaN に変わります。数が 2 倍になることはありません。
  • 初期値は無限大または NaN です。この場合、指数フィールドは 255 または 2047 で始まり、それに 1 を追加するとゼロに変更されます (符号ビットにオーバーフローする可能性があります)。結果はゼロまたは非正規ですが、それぞれ無限大または NaN になります。

(上記は正符号の場合です。状況は負符号と対称です。)

他の人が指摘しているように、一部のプロセッサには、浮動小数点値のビットをすばやく操作する機能がありません。その場合でも、指数フィールドは他のビットから分離されていないため、通常、上記の最後のケースで符号ビットにオーバーフローすることなく、指数フィールドに 1 を追加することはできません。

アプリケーションによっては、サブノーマルや NaN、さらには無限大を無視するなどのショートカットを許容できるものもありますが、アプリケーションがゼロを無視できることはめったにありません。指数に1を足すと0をうまく扱えないので使えません。

于 2012-10-16T17:00:45.390 に答える
10

コンパイラーやコンパイラー作成者が賢くないということではありません。それは、標準に従い、Infs、Nans、denormals などの必要なすべての「副作用」を生成するようなものです。

また、メモリの読み取りなど、求められていない他の副作用を生じさせないことも重要です。しかし、状況によってはより高速になる可能性があることは認識しています。

于 2012-10-16T16:26:43.340 に答える
3

実際、これはハードウェアで起こっていることです。

また2、仮数が 1.0、指数が 2^1 の浮動小数点数として FPU に渡されます。乗算では、指数が加算され、仮数が乗算されます。

複雑なケース (2 のべき乗ではない値で乗算) を処理するための専用ハードウェアがあり、特別なケースが専用ハードウェアを使用する場合よりも悪く処理されないことを考えると、回路や命令を追加する意味はありません。 .

于 2012-10-17T06:14:40.980 に答える
0

2 の累乗による乗算に関する以前の Stackoverflow の質問。コンセンサスと実際の実装により、残念ながら、標準の乗算よりも効率的な現在の方法がないことが証明されました。

于 2012-11-14T09:12:59.323 に答える
0

組み込みシステムのコンパイラが、問題のマシンに最適な方法でコード ジェネレーターによって変換できる特別な 2 の累乗の疑似演算を持つと便利な場合があります。指数は、完全な 2 の累乗の乗算を実行するよりも桁違いに高速かもしれませんが、乗算が最も遅い組み込みマイクロでは、コンパイラは浮動小数点乗算ルーチンにその引数をチェックさせることで、おそらくより大きなパフォーマンスの向上を達成できます。ゼロである仮数の部分をスキップするように実行時に。

于 2012-10-17T02:52:46.600 に答える
0

2 を掛けると指数が 1 増えると思うなら、考え直してください。IEEE 754 浮動小数点演算で考えられるケースは次のとおりです。

ケース 1: 無限大と NaN は変更されません。

ケース 2: 指数を増やし、符号ビットを除く仮数部を 0 に設定することにより、可能な限り最大の指数を持つ浮動小数点数を無限大に変更します。

ケース 3: 指数が可能な最大指数より小さい正規化された浮動小数点数は、指数が 1 増加します。ヤッピー!!!

ケース 4: 最上位の仮数ビットが設定された非正規化浮動小数点数は、指数が 1 増加し、正規化された数値になります。

ケース 5: 最大仮数ビットがクリアされた非正規化浮動小数点数 (+0 と -0 を含む) は、仮数が 1 ビット位置だけ左にシフトされ、指数は変更されません。

これらすべてのケースを正しく処理する整数コードを生成するコンパイラが、プロセッサに組み込まれた浮動小数点と同じくらい高速になるとは思えません。また、2.0 による乗算にのみ適しています。4.0 または 0.5 による乗算には、まったく新しい一連のルールが適用されます。また、2.0 による乗算の場合、x * 2.0 を x + x に置き換えようとする場合があり、多くのコンパイラがこれを行っています。つまり、プロセッサは、たとえば、1 つの加算と 1 つの乗算を同時に実行できますが、それぞれの種類を 1 つずつ実行できない可能性があるためです。そのため、他の操作を同時に行う必要があるかどうかに応じて、x * 2.0 を好む場合もあれば、x + x を好む場合もあります。

于 2015-03-11T17:12:51.587 に答える