web-dev-qa-db-ja.com

整数を3で割る最も速い方法は何ですか?

int x = n / 3;  // <-- make this faster

// for instance

int a = n * 3; // <-- normal integer multiplication

int b = (n << 1) + n; // <-- potentially faster multiplication
34
Greg Dean

これは、出力プロセッサーに依存する場合はコンパイラーが最適化するため、最速です。

int a;
int b;

a = some value;
b = a / 3;
61
KPexEA

「コンパイラーに任せる」と言った人は正しかったが、私は彼を改造したりコメントしたりする「評判」を持っていない。 gccにint test(int a){return a/3;をコンパイルするように依頼しました。 }はix86の場合で、出力を逆アセンブルします。ちょうど学術的な興味のために、それがしていることはおおよそに0x55555556を掛け、それからその64ビット結果の上位32ビットを取ることです。あなたはこれをあなた自身に示すことができます:

 $ Ruby -e 'puts(60000 * 0x55555556 >> 32)' 
 20000 
 $ Ruby = -e 'puts(72 * 0x55555556 >> 32)' 
 24 
 $ 

Montgomery Division のウィキペディアのページは読みにくいですが、幸い、コンパイラーの担当者が読んでいるので、そうする必要はありません。

122
Martin Dorey

値の範囲がわかっている場合、たとえば、符号付き整数を3で除算していて、除算される値の範囲が0〜768であることがわかっている場合、より高速な方法があります。係数でそれを左に2の累乗でシフトし、その係数を3で割ったもの。

例えば。

範囲0-> 768

あなたは10ビットのシフトを使用することができ、それは1024で乗算し、3で除算したいので、乗数は1024/3 = 341になるはずです

(x * 341)>> 10を使用できるようになりました
(符号付き整数を使用する場合、シフトが符号付きシフトであることを確認してください)、また、シフトが実際のシフトであり、ビットROLLではないことを確認してください

これにより、値3が効果的に除算され、標準のx86/x64 CPUでの自然な除算3の約1.6倍の速度で実行されます。

もちろん、コンパイラーができないときにこの最適化を行うことができる唯一の理由は、コンパイラーがXの最大範囲を認識していないため、この決定を行うことができないためです。

場合によっては、値をより大きな値に移動してから同じことを行うほうが有益なこともあります。フルレンジのintがある場合、64ビット値にして、3で除算する代わりに乗算とシフトを実行できます。

私は最近、画像処理を高速化するためにこれを行わなければなりませんでした。各カラーチャネルにバイト範囲(0〜255)を持つ3つのカラーチャネルの平均を見つける必要がありました。赤、緑、青。

最初は私は単に使用しました:

平均=(r + g + b)/ 3;

(したがって、各チャネルはバイト0-255であるため、r + g + bの最大値は768、最小値は0です)

数百万回の繰り返しの後、操作全体で36ミリ秒かかりました。

私は行を次のように変更しました:

avg =(r + g + b)* 341 >> 10;

そしてそれはそれを22ミリ秒に短縮しました、少しの工夫で何ができるかは驚くべきことです。

この高速化は、最適化をオンにしていて、IDEを介さずにデバッグ情報なしでプログラムをネイティブに実行していたにもかかわらず、C#で発生しました。

22

FPGAの算術演算の実行に焦点を当てた、3でより効率的に除算する方法の詳細については、 で除算する方法 を参照してください。

また、関連:

11
Jay

プラットフォームやCコンパイラに応じて、使用するだけのようなネイティブソリューション

y = x / 3

高速または非常に低速になる可能性があります(除算が完全にハードウェアで行われる場合でも、DIV命令を使用して行われる場合、この命令は最新のCPUでの乗算より約3〜4倍遅くなります)。最適化フラグがオンになっている非常に優れたCコンパイラは、この操作を最適化できますが、確実にしたい場合は、自分で最適化することをお勧めします。

最適化のためには、既知のサイズの整数を持つことが重要です。 Cでは、intには既知のサイズがないため(プラットフォームやコンパイラによって異なる場合があります!)、C99の固定サイズの整数を使用することをお勧めします。以下のコードは、符号なし32ビット整数を3で除算し、Cコンパイラが約64ビット整数を知っていることを前提としています(注:32ビットCPUアーキテクチャでも、ほとんどのCコンパイラ64ビット整数をうまく処理できます):

static inline uint32_t divby3 (
    uint32_t divideMe
) {
    return (uint32_t)(((uint64_t)0xAAAAAAABULL * divideMe) >> 33);
}

これは奇妙に聞こえるかもしれませんが、実際には上記の方法は3で除算します。そうするために必要なのは、単一の64ビット乗算とシフトだけです(私が言ったように、乗算はCPUでの除算より3〜4倍速いかもしれません) )。 64ビットアプリケーションでは、このコードは32ビットアプリケーションよりもはるかに高速です(32ビットアプリケーションでは、2つの64ビット数を乗算すると、32ビット値で3つの乗算と3つの加算が行われます)。ただし、 32ビットマシンでの除算。

一方、コンパイラが非常に優れていて、定数による整数除算を最適化する方法を知っている場合(最新のGCCは確認済みです)、とにかく上記のコードを生成します(GCCはこのコードを正確に作成します)少なくとも最適化レベルを有効にする場合は「/ 3」1)。他のコンパイラの場合...この方法は十分に文書化されており、インターネット上のあらゆる場所で言及されていますが、そのようなトリックを使用することは期待できません。

問題は、定数に対してのみ機能し、変数に対しては機能しないことです。マジックナンバー(ここでは0xAAAAAAAB)と乗算後の正しい演算(ほとんどの場合、シフトまたは加算、あるいはその両方)を常に知る必要があり、どちらも除算したい数値によって異なり、両方にCPU時間を要します。それらをその場で計算します(ハードウェアの除算よりも遅くなります)。ただし、コンパイラーがコンパイル時にこれらを計算するのは簡単です(1秒程度のコンパイル時間はほとんど役割を果たしません)。

10
Mecki

64ビットの数値の場合:

uint64_t divBy3(uint64_t x)
{
    return x*12297829382473034411ULL;
}

ただし、これは予想される整数の除算ではありません。数値が3で割り切れる場合は正しく機能しますが、割り切れない場合は非常に大きな数値を返します。

たとえば、11で実行すると、6148914691236517209が返されます。これはごみのように見えますが、実際には正しい答えです。3を掛けると、11が返されます。

切り捨てる除算を探す場合は、/演算子を使用します。私はあなたがそれよりもずっと速く得ることができるとはとても疑います。

理論:

64ビットの符号なし演算は、2 ^ 64を法とする算術演算です。これは、2 ^ 64モジュラス(基本的にはすべて奇数)と互いに素である整数ごとに、除算の代わりに乗算するために使用できる乗法逆数が存在することを意味します。このマジック番号は、拡張ユークリッドアルゴリズムを使用して3*x + 2^64*y = 1方程式を解くことで取得できます。

4
Calmarius

もしあなたが 本当に 乗算または除算したくないですか?ここに私が考案した近似があります。 (x/3)=(x/4)+(x/12)なので機能します。しかし、(x/12)=(x/4)/ 3なので、十分に良くなるまでプロセスを繰り返す必要があります。

#include <stdio.h>

void main()
{
    int n = 1000;
    int a,b;
    a = n >> 2;
    b = (a >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    b = (b >> 2);
    a += b;
    printf("a=%d\n", a);
}

結果は330です。b=((b + 2)>> 2);を使用すると、より正確になります。丸めを説明します。

areの乗算が許可されている場合は、(1/3)の適切な近似値を2のべき乗の除数で選択します。たとえば、n *(1/3)〜= n * 43/128 =(n * 43)>> 7。

この手法は、 インディアナで最も役立ちます。

3
Steve Hanov

高速かどうかはわかりませんが、ビット単位の演算子を使用して2進除算を実行する場合は、 このページ で説明されているシフトと減算の方法を使用できます。

  • 商を0に設定
  • 被除数と除数の左端の桁を揃える
  • 繰り返す:
    • 被除数の除数より上の部分が除数以上の場合:
      • 次に、被除数のその部分から除数を減算し、
      • 1を商の右端に連結
      • それ以外の場合、商の右端に0を連結します
    • 除数を1つ右にシフト
  • 配当が除数を下回るまで:
  • 商は正しいですが、配当は残りです
  • やめる
2
Mark Cidade

整数除算 に関するこの記事を本当に見たいのですが、それは学術的なメリットしかありません...その種のトリックの恩恵を受けて実際に実行する必要があるのは興味深いアプリケーションでしょう。

1
Rob Walker

本当に大きな整数除算(たとえば、64ビットより大きい数値)の場合、数値をint []として表し、一度に2桁を取り、3で除算することにより、非常に高速に除算を実行できます。残りは、次の2桁の一部になります。など。

例えば。 11004/3あなたが言う

11/3 = 3、残り= 2(11-3 * 3から)

20/3 = 6、残り= 2(20-6 * 3から)

20/3 = 6、残り= 2(20-6 * 3から)

24/3 = 8、残り= 0

したがって、結果668

internal static List<int> Div3(int[] a)
{
  int remainder = 0;
  var res = new List<int>();
  for (int i = 0; i < a.Length; i++)
  {
    var val = remainder + a[i];
    var div = val/3;

    remainder = 10*(val%3);
    if (div > 9)
    {
      res.Add(div/10);
      res.Add(div%10);
    }
    else
      res.Add(div);
  }
  if (res[0] == 0) res.RemoveAt(0);
  return res;
}
1
Carlo V. Dango

簡単な計算...最大n回の反復(nはビット数):

uint8_t divideby3(uint8_t x)
{
  uint8_t answer =0;
  do
  {
    x>>=1;
    answer+=x;
    x=-x;
  }while(x);
  return answer;
}
0
Albert Gonzalez

一部のアーキテクチャでは、ルックアップテーブルアプローチも高速になります。

uint8_t DivBy3LU(uint8_t u8Operand)
{
   uint8_t ai8Div3 = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, ....];

   return ai8Div3[u8Operand];
}
0
Luke