web-dev-qa-db-ja.com

数値の絶対値を取得する最も速い方法はどれですか

数値の絶対値を返す演算を実装する最も速い方法はどれですか?

x=root(x²)

または

if !isPositive(x):
    x=x*(-1)

実際、この質問は、ifの速度(および理由)として翻訳できます。

大学の教授は常にifsは非常に遅いため、避けるようにと言ってきましたが、私は常にどれほど遅いのか、そしてなぜなのかを尋ねるのを忘れていました。ここの誰かが知っていますか?

40
Diones

条件文は、単純な算術演算よりも遅くなりますが、平方根を計算するような愚かなものよりはるかに高速です。

私の議会時代の経験則:

  • 整数またはビットごとの演算:1サイクル
  • 浮動小数点加算/サブ/マルチ:4サイクル
  • 浮動小数点div:〜30サイクル
  • 浮動小数点のべき乗:〜200サイクル
  • 浮動小数点sqrt:実装によっては最大60サイクル
  • 条件付きブランチ:平均。 10サイクル、十分に予測されている場合はより良い、誤って予測されている場合はさらに悪い
63
kquinn

Ifステートメントを使用せずに2の補数の整数の絶対値を計算する素晴らしいトリックがあります。理論では、値が負の場合はビットを切り替えて1を追加し、そうでない場合はビットをそのまま渡します。 A XOR 1はたまたまAを切り替え、A XOR 0はたまたまAをそのまま残します。したがって、次のようなことをしたいとします:

  uint32_t temp = value >> 31;     // make a mask of the sign bit
  value ^= temp;                   // toggle the bits if value is negative
  value += temp & 1;               // add one if value was negative

原則として、3つのアセンブリ命令(分岐なし)で実行できます。そして、math.hで取得したabs()関数が最適に実行すると考えたいと思います。

分岐なし==パフォーマンスが向上します。上記の@paxdiabloの応答とは対照的に、これは、コード内のブランチが多いほどブランチプレディクターが間違ってロールバックしなければならない可能性が高い深いパイプラインで本当に重要です。可能な場合、物事はコアでフルスロットルで動き続けます:)。

78
vicatcu

ええと、先生たちは実際にそう言ったのですか?ほとんどの人が従うルールは、最初にコードを読みやすくし、次にパフォーマンスの問題を微調整することです それらが実際に問題であることが証明された後。 使用するifステートメントが多すぎるため、99.999%の確率でパフォーマンスの問題が発生することはありません。 Knuthが最善と述べた 、「時期尚早な最適化はすべての悪の根源です」。

27
Ed S.

平方根の計算は非常に遅いため、おそらくあなたができる最悪のことの1つです。通常、これを行うためのライブラリ関数があります。 Math.Abs​​()のようなもの。 -1との乗算も不要です。 -xを返すだけです。したがって、適切な解決策は次のとおりです。

(x >= 0) ? x : -x

コンパイラはおそらくこれを単一の命令に最適化します。実行パイプラインが長いため、最新のプロセッサでは条件が非常に高くなる可能性があります。分岐が予測されず、プロセッサが誤ったコードパスから命令の実行を開始した場合、計算は破棄される必要があります。ただし、前述のコンパイラの最適化のため、この場合は気にする必要はありません。

12

完全を期すために、C++のx86システム上のIEEE浮動小数点数でこれを行う方法を次に示します。

*(reinterpret_cast<uint32_t*>(&foo)) &= 0xffffffff >> 1;
5
awdz9nld

ifバリアントは、通常、平方根と比較して盲目的に高速になります。これは、通常、マシンコードレベルの条件付きジャンプ命令に変換されるためです(式の評価後、複雑になる可能性がありますが、0未満の単純なチェックであるため、この場合はそうではありません。

数値の平方根をとると、はるかに遅くなる可能性があります(たとえば、ニュートンの方法では、マシンコードレベルでmany manyifステートメントを使用します)。

混乱の原因として考えられるのは、ifが常に、命令ポインターを非順次的に変更することになるという事実です。これにより、アドレスが予期せず変更されたときにパイプラインを再設定する必要があるため、パイプラインに命令をプリフェッチするプロセッサの速度が低下する可能性があります。

ただし、そのコストは、単純なチェックアンドネゲートとは対照的に、平方根演算を実行する場合に比べてごくわずかです。

4
paxdiablo

数値の絶対値を取得する最も速い方法はどれですか

「正しい」答えは実際にはここにはないと思います。絶対数を取得する最も速い方法は、おそらくIntel Intrinsicを使用することです。 https://software.intel.com/sites/landingpage/IntrinsicsGuide/ を参照して、「vpabs」(またはCPUの機能を果たす別の組み込み関数)を探します。ここで他のすべてのソリューションを打ち負かすと確信しています。

コンパイラ組み込み関数が気に入らない(または使用できない、または...)場合は、コンパイラが「ネイティブ絶対値」(_std::abs_の呼び出しをC++またはMath.Abs(x) in C#)は自動的に組み込み関数に変更されます-基本的には、逆アセンブル(コンパイル)されたコードを確認する必要があります。 JITを使用している場合は、JIT最適化が無効になっていないことを確認してください。

それでも最適化された指示が得られない場合は、ここで説明されている方法を使用できます: https://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

3
atlaste

剰余演算は剰余を見つけるために使用され、絶対値を意味します。 !pos(x)であり、x = x * -1である必要があるため、質問を変更しました。 (欠落していない)

Ifステートメントの効率について心配する必要はありません。代わりに、コードの読みやすさに焦点を当ててください。効率の問題があることが判明した場合は、コードのプロファイリングに集中して、実際のボトルネックを見つけます。

コーディング中に効率を監視したい場合は、アルゴリズムのbig-Oの複雑さについてのみ心配する必要があります。

ステートメントが非常に効率的である場合は、式を評価し、その条件に基づいて プログラムカウンター を変更します。プログラムカウンタは、次に実行される命令のアドレスを格納します。

-1による多重化と、値が0より大きいかどうかのチェックは、どちらも単一のAssembly命令に削減できます。

数値の根を見つけ、その数値を最初に2乗することは、否定のifよりも明らかに多くの演算です。

2
Brian R. Bondy

平方根を実行するのにかかる時間は、条件付きを実行するのにかかる時間よりもはるかに長くなります。条件文が遅いために条件文を回避するように教えられている場合、誤解されています。これらは、整数の加算や減算、ビットシフトなどの簡単な操作よりもかなり遅くなります。そのため、このような簡単な操作を実行している場合にのみ、ループの展開が効果を発揮します。しかし、物事の壮大な計画では、条件文は良くて速く、悪くて遅くはありません。条件付きステートメントを回避するために関数を呼び出したり平方根を計算したりするような複雑なことをするのはクレイジーです。

また、(x = x * -1)の代わりに(x = 0-x)を実行しないのはなぜですか?多分コンパイラは同じようにそれらを最適化しますが、とにかく2番目のものはもっと簡単ではありませんか?

1
thomasrutter

8086アセンブリを使用していますか? ;-)

                ; abs value of AX
   cwd          ; replicate the high bit into DX
   xor  ax, dx  ; take 1's complement if negative; no change if positive
   sub  ax, dx  ; AX is 2's complement if it was negative The standard
                : absolute value method works on any register but is much
                ; slower:

   or   bx, bx  ; see if number is negative
   jge  notneg  ; if it is negative...
   neg  bx      ; ...make it positive
notneg:         ; jump to here if positive

(猛烈に 盗まれた

1
Mark Maxham

2つの数値の絶対値を単に比較する場合(たとえば、比較後にどちらの絶対値も必要ない場合)、両方の値を二乗して両方を正にします(各値の符号を削除します)。小さい方の正方形より大きい。

0
Neoheurist

負の数のリストの場合:

メモリにゼロが格納されている場合は、単に0 - x、ここでxは負の数です。

または、メモリにゼロが格納されていない場合:

x-x-x、ここでxは負の数です。

または、わかりやすくするために角かっこを使用します。

(x) - (x) - (x)=>(-n) - (-n) - (-n)、 どこ x = -n

つまり、それ自体から負の数を減算してゼロを取得し、次にゼロから減算します。

0
Code Bag

より高速なものは、対象とするコンパイラとCPUに大きく依存します。ほとんどのCPUとすべてのコンパイラでx =(x> = 0)? x:-x;絶対値を取得する最速の方法ですが、実際には、多くの場合、標準関数がすでにこのソリューションを提供しています(例:fabs())。これは、条件付きジャンプではなく、比較に続いて条件付き代入命令(CMOV)にコンパイルされます。ただし、一部のプラットフォームにはその命令がありません。ただし、Intel(MicrosoftまたはGCCではない)コンパイラーは、if()を条件付き割り当てに自動的に変換し、(可能な場合は)サイクルの最適化を試みます。

CPUが統計的予測を使用する場合、分岐コードは一般に条件付き割り当てよりも遅くなります。 if()は、操作が複数回繰り返され、条件の結果が常に変化している場合、平均して遅くなる可能性があります。 IntelのようなCPUは、bothブランチの計算を開始し、無効なブランチを削除します。大きなif()ボディまたは多数のサイクルの場合重要かもしれません。

最新のIntel CPUのsqr()とsqrt()は、単一の組み込み命令で低速ではありませんが、不正確であり、レジスターのロードにも時間がかかります。

関連質問: なぜCPU分岐命令が遅いのですか?

おそらく、教授は学生にこの問題についての研究を望んでいました。それは、学生が自主的に考え、追加の情報源を探すことを学んだ場合にのみ有効な半挑発的な質問です。

0

8088/8086のCでいくつかのレトロなグラフィックプログラミングを行っており、abs()の呼び出しには時間がかかるため、次のように置き換えました。

/* assuming 'i' is int; this WILL NOT WORK on floating point */
if (i < 0) {
    i = ~i + 1;
}

これがより速い理由は、アセンブリのCALLJNEと本質的に交換するためです。メソッドを呼び出すと、いくつかのレジスタが変更され、さらにいくつかのレジスタがプッシュされ、引数がスタックにプッシュされ、プリフェッチキューをフラッシュできます。さらに、これらのアクションは関数の最後で元に戻す必要があり、これはすべてCPUにとって非常にコストがかかります。

0