11

x%25を計算しているコードがあります。xは常に正の値を取りますが、ダイナミックレンジが大きくなっています。

私は、ax%25を計算するこの特定のコード部分が大きなサイクルを取っていることを知りました。最適化する必要があります。

テーブルのメモリサイズが大きくなる可能性があるため、事前に計算されたルックアップテーブルは除外されます。

2番目のアプローチとして、以下のフラグメントをコーディングしました(Cコード)-

mod(a, b)
{   
    int r = a;  
    while(r >= b)
    {      
        r = r - b;
    }   
    return r;
}

1.)このコードをサイクルごとにさらに最適化するにはどうすればよいですか(最大に絞る)?

2.)x%25を達成するためのまったく異なる最適化された方法はありますか(私はそれが一般的な操作ではないことを知っていますが、それでも、人々が私を悩ますかもしれない彼らの経験で使用したかもしれない巧妙な入力を探しています。)

ありがとうございました。

-広告

編集:

Cでネイティブのモジュロ演算子%を使用すると、内部で除算演算(/)を使用しますが、これは使用しているプロセッサでコストがかかります(div命令なし)。したがって、カスタム実装が%演算子を使用して固有の計算を打ち負かすことができるかどうかを確認しようとしています。

-広告

4

22 に答える 22

32

Hacker's Delightを読むことをお勧めします。定数除数の非常に高速な剰余アルゴリズムについて説明します。彼らはほぼ確実に一般的なアルゴリズムを打ち負かします.

更新: ここにいくつかのサンプル コードがあります...おそらく、一時的な long long を回避するために作り直すことができます。

unsigned mod25(unsigned n)
{
    unsigned reciprocal = 1374389535; // 2^35 / 25
    unsigned div25 = ((unsigned long long)n * reciprocal) >> 35;
    return n - div25 * 25;
}
于 2009-06-11T13:11:58.810 に答える
9

これが私が思いついた別の解決策です:

int mod25(int x){
  /* 25 * (all powers of 2 <= INT_MAX), descending */
  if (x >= 1677721600) x -= 1677721600;
  if (x >=  838860800) x -=  838860800;
  if (x >=  419430400) x -=  419430400;
  if (x >=  209715200) x -=  209715200;
  if (x >=  104857600) x -=  104857600;
  if (x >=   52428800) x -=   52428800;
  if (x >=   26214400) x -=   26214400;
  if (x >=   13107200) x -=   13107200;
  if (x >=    6553600) x -=    6553600;
  if (x >=    3276800) x -=    3276800;
  if (x >=    1638400) x -=    1638400;
  if (x >=     819200) x -=     819200;
  if (x >=     409600) x -=     409600;
  if (x >=     204800) x -=     204800;
  if (x >=     102400) x -=     102400;
  if (x >=      51200) x -=      51200;
  if (x >=      25600) x -=      25600;
  if (x >=      12800) x -=      12800;
  if (x >=       6400) x -=       6400;
  if (x >=       3200) x -=       3200;
  if (x >=       1600) x -=       1600;
  if (x >=        800) x -=        800;
  if (x >=        400) x -=        400;
  if (x >=        200) x -=        200;
  if (x >=        100) x -=        100;
  if (x >=         50) x -=         50;
  if (x >=         25) x -=         25;
  return x;
}

これは除算や乗算を使用せず、27 回の比較と最大 27 回の減算のみを使用します。

これが機能することを自分で納得させるのは少し難しいですが、機能します (少なくとも x の負でない値の場合)。

上記のコードは、実際にはこれを展開したバージョンです。

int mod25(int x){
  int divisor;
  for(int divisor = 1677721600; divisor >= 25; divisor >>= 1) {
    if (x >= divisor) x -= divisor;
  }
  return x;
}

それをアンロールすることで、ループの比較と、より大きなコードを犠牲にしてシフトを行うことを回避します。気が向いたら、ダフのデバイスを使用して部分的に展開することもできますが、合計で 27 回の反復しかなく、反復ごとのコードが非常に小さいため、完全に展開する傾向があります。

どのように動作するか: すべての負でない整数 x は (n * 25) + k として表すことができます。ここで、n は負でない整数であり、k は 0 から 24 までの整数です。k はたまたま、必要な結果でもあります。したがって、x - (n * 25) を計算できれば、答えが得られます。ただし、事前に n を知らなくても、これを実行できるようにしたいと考えています。

n を 2 進数で考えてみましょう。1 の各ビットをオフにできる場合は 0 になります。これを行う 1 つの方法は、2 の大きなべき乗から始めて、2 の各べき乗を減算し、現在の n の値が より大きい場合にのみ、2 のべき乗を減算することです。またはその 2 の累乗に等しい。

(n * 25) を扱っているので、実際には 2 かける 25 の降べき乗が必要です。k は厳密に 25 未満であり、考慮した最小の除数は 25 であるため、(n * 25) + k.

したがって、各比較 + 減算は n の 1 ビットをゼロにし、最後に残りの k が残ります。

于 2009-06-12T18:42:12.783 に答える
9

私は Pax の答えに触発され、より汎用的なアルゴリズムを作成しました。

int mod(int a, int b) {
    int s = b;
    while (s <= a) {
        s <<= 1;
    }
    int r = a;
    while (r >= b) {
        s >>= 1;
        if (s <= r) {    
            r -= s;
        }
    }
    return r;
}

これは、結果が見つかるまでbfromの 2 乗の倍数を減算します。a

編集:if正しく機能させるための条件を追加しました。

例として、これが 100 % 7 を実行している場合、最初に 7 * 2 * 2 * 2 * 2 = 112 が計算されます。次に、112 ( ) を 2 で割り、それを 100 ( )sから引きます( の場合) 。モジュロが見つかるまでこれ。したがって、rs <= r

s = 112 / 2 = 56, r = 100 - 56 = 44
s = 56 / 2 = 28, r = 44 - 28 = 16
s = 28 / 2 = 14, r = 16 - 14 = 2

したがって、100 % 7 = 2

于 2009-06-11T12:54:36.123 に答える
7

これが私が思いつくことができる最高のものです:

int mod25(int x)
{
    while((x = (x & 31) + 7 * (x >> 5)) >= 25)
        x -= 25;

    return x;
}

で近似x % 25x % 32 + 7 * (x/32)ます。値は の倍数だけオーバーシュート25するため、再帰が可能になります。

パフォーマンスは十分にあるようです: x = 2147483647(aka INT_MAX) の値には 11 回の反復が必要です。

于 2009-06-11T15:13:45.023 に答える
7

定数によるモジュラスが必要なため、逆数の乗算を使用してそれを打ち負かすことができます。このペーパーでは、そのような方法で定数で割る方法と、最後に、それから剰余を取得する方法を示します。

于 2009-06-11T12:42:24.653 に答える
7

ああ、私の<選択の神>。これらの答えのいくつかは信じられません。

まず第一に、引き算の繰り返しは、たとえ Pax のバージョンであっても、決して最適になることはありません。次のことを考慮してください。

20 % 25

これは、減算を繰り返すことで簡単かつ迅速に行うことができますが、次のようになります。

65535 % 25

恐ろしく遅くなり、600回以上の反復になります。これは、16 ビットの数値で平均 300 回の反復です。32ビット数に関しては、そこに行かないでください。

これを行う最も速い方法は、長い除算を使用することです。ニキの答えを見てください。

しかし、これはとにかくコンパイラーが生成するものです。少なくとも、コンパイラーが生成しているものであることを願っています。ニッチ プロセッサ用のコンパイラを使用しているかどうかを常に確認することをお勧めします。

これを高速化する最善の方法は、最初からモジュラスを実行しないことです。モジュラスを取得する必要があるのはなぜですか。コード/アルゴリズムをリファクタリングして、モジュラスを回避するか、少なくともモジュラスを自明にすることができますか。

于 2009-06-11T12:46:40.947 に答える
5

ループの問題は、それが O(n) であることです。r の値が大きいと非常に遅くなります。私は次のようなことを提案します:

for (int s = MAX_SHIFT; s>=0; s--)
  if (r > (b<<s)) r -= (b<<s);

しかし、あなたのコンパイラがそれよりもはるかに高価なことをしているとは思えません。

于 2009-06-11T12:35:27.463 に答える
3

多くのプロセッサでは、整数乗算は整数除算より高速です。このブログ投稿では、定数整数除算を定数整数乗算に置き換える方法を示します。数学を少し並べ替えると、商の代わりに剰余を得ることができます。ただし、適度に洗練されたコンパイラを使用している場合、これは既に行われていることに注意してください。書くだけx % 25で、残りはコンパイラーが処理します。C でこの最適化を行う前に、生成されたアセンブリ コードをチェックして、コンパイラがまだこれを行っていないことを確認する必要があります。 .

ループは、かなり大きなオペランドに対してネイティブ命令を使用して除算を行うよりもはるかに遅くなります。

編集:この論文も参照してください。

于 2009-06-11T13:10:09.050 に答える
3

C コンパイラが除算命令のない CPU をターゲットにしている場合は、次のようにコードを変更できます。

mod(a, b) {
    int s = b + b + b + b;
    int r = a;
    while(r >= s) {
        r -= s;
    }
    while(r >= b) {
        r -= b;
    }
    return r;
}

これは、値を 1 ではなく 4 のチャンクで減算することによって機能し、最後の値までは、1 のチャンクの減算に切り替わります。

これにより、コードが約 4 倍速く実行されるはずです (4*b整数の範囲外ではないことを前提としています)。さらに高速化するために、ループ8*bの前に複数のループ (1 つなど) を挿入することもできます。4*b

それ以外には、アセンブラーのハンドコーディングが役立つかもしれませんが、それがなくても上記のコードからかなりのブーストが得られると思います。

mod 呼び出しの使用方法について詳しく知っている場合は、特定のケースに合わせて最適化できます。たとえば、16 ビット整数のモジュロ 25 だけを知りたい場合、次のコードは変数分母を使用した単純なループよりもはるかに高速です。

int mod25 (int a) {                // a has maximum value of 2^15-1 = 32767
    while (a >= 15625) a-= 15625;  // at most 2 times.
    while (a >= 625) a-= 625;      // at most 24 times.
    while (a >= 25) a-= 25;        // at most 24 times.
    return a;
}

%テストを実行すると、モジュロ コードと演算子の使用 (2 秒対 0 秒)の間に顕著な違いが現れるまでに、1000 万回の反復を実行する必要があることがわかりました。その時点までは両方とも 0 秒でしたが、これは高速なマシン (より優れているmod25) と命令div(オペレーターにとってより優れている) で実行された%ため、独自のハードウェアでベンチマークする必要があります。

これは、コードを読めなくすることなく得られる可能性が高い速度とほぼ同じです (ただし、それがどのように機能するかを説明するコメントをたくさん追加したい場合は、それでも停止することはありません)。

分母のより一般的な解決策は、最初に分母を可能な限り 2 倍にし (速度のためにビット シフトを使用)、その後の減算が最小になるようにすることです。次に、分子が増加した分母を下回ったら、分母を半分にして続行します (分母が最初に戻るまで)。

int mod (int n, int d) {
    /* dx is the adjusted denom, don't let it overflow though. */
    int dx = d;
    while (((dx << 1) >>1) == dx)
        dx <<= 1;

    /* This loop processes the dx values until they get too small. */
    while (dx >= d) {
        /* This loop subtracts the large dx value. */
        while (n >= dx)
            n -= dx;
        dx >>= 1;
    }
    return n;
}

mod25これは、より一般的なソリューションを提供しながら、実際には上記の最適化されたバージョンと同等のパフォーマンスを発揮します。

于 2009-06-11T12:22:22.940 に答える
1

%オペレーターが気に入らない場合:

int mod(int a, int b) {
    int integral = a / b;
    return a - (b*integral);
}
于 2009-06-11T12:08:00.447 に答える
1
int mod25(int x) {
  static int divisors[] = {2147483625, 244140625, 9765625, 390625, 15625, 625, 25};
  int i;
  for (i = 0; i < sizeof(divisors)/sizeof(int); i++) {
    int divisor = divisors[i];
    while (x >= divisor) {
      x -= divisor;
    }
  }
  return x;
}

仕組み:xできるだけ早く値を減らすために、25 の大きな倍数で減分したいと考えています。除数が大きすぎる場合は、より小さい 25 の倍数に切り替えます。除数が既に 25 に下がっている場合は、完了です。

さまざまな除数を試してみることができます。次のことを確認してください。

  • 彼らは下降している
  • それらはすべて25の倍数です
  • 最後の値は 25 です

上記のコードでは、最大の符号付き 32 ビットの 25 の倍数と 25 のべき乗を使用しました。これは妥当と思われますが、それが最適かどうか確信が持てないことは認めざるを得ません。

(ところで: コンパイラが定数の折りたたみを行わない場合 (これは非常に驚くべきことです)、上限をiハードコードされた定数に置き換えることをお勧めします。)

于 2009-06-12T01:34:52.783 に答える
1

おそらく最速ではありませんが、かなり効率的です。テストする時間はありませんが、(2 の累乗) * 25 から最大範囲/2 までのルックアップ テーブルを使用します。次に、ループを実行します。たとえば、3199 までの範囲には 7 回の繰り返しが必要です。

static int pow[] = {25, 50, 100, 200, 400, 800, 1600};

int mod25(int x)
{    
    int i = sizeof pow /sizeof pow[0];

    while (i--)
    {
        if (x >= pow[i])
            x -= pow[i];    
    }    
    return x;
}

範囲が非常に広いが、低い値がより一般的である場合は、バイナリ チョップを使用して開始点を見つけることをお勧めします。

于 2009-06-11T12:57:04.930 に答える
1

2 の累乗になることがわかっている場合は、モジュロ演算子の代わりにbビット単位を使用できます。ANDただし、モジュロのウィキペディアのページは、Cコンパイラがこれに気づき、とにかくモジュロを最適化することを示しているようです。

于 2009-06-11T12:19:10.683 に答える
0

演算子を使用できないのはなぜ%ですか? これが C コードで、数値が通常の「ネイティブ」intの :s である場合、それが断然最速の方法です。

于 2009-06-11T12:07:36.517 に答える
0

C の組み込みモジュラス演算子を使用できない理由はありますか?

int a = x % 25;

あなたの編集に続いて;

あなたのプロセッサにモジュロサポートが組み込まれていない場合でも、問題のプロセッサにネイティブの % 関数がないことをコンパイラが認識し、それを最適にエミュレートする asm コードを生成する可能性が高いという単純な理由で、% 演算子を使用します。

このように言えば、コンパイラが組み込み演算子を使用して生成するものよりも優れた一般的なアルゴリズムを考え出すことができれば、私は魅了されます。

于 2009-06-11T12:10:23.123 に答える
0

x % 25操作に非常に長い時間がかかるのはかなり奇妙だと思います(組み込みの%演算子を使用している場合)。最新のプロセッサのほとんどは、これを 1 つの命令で行う必要があります。このコードに時間がかかる他の理由を探します。

編集:これは、少なくともいくつかのアイデアを与える可能性のあるアルゴリズムです:

256 = 6 (mod 25)

これは、数値xをバイトとして書き込むと、それx3 x2 x1 x0が得られることを意味しますx = 6^3*x3 + 6^2*x2 + 6*x1 + x0(mod 25)

これにより、 のサイズを縮小するアルゴリズムが得られますx

int x0 = x & 0xFF, x1 = (x>>8) & 0xFF, x2 = (x>>16) & 0xFF, x3 = (x>>24) & 0xFF;

int y = x4;
y = (y << 2) + (y << 1) + x3;
y = (y << 2) + (y << 1) + x2;
y = (y << 2) + (y << 1) + x1;
y = (y << 2) + (y << 1) + x0;

(ここ(y << 2) + (y << 1) = 4*y + 2*y = 6*y)

この後、剰余はmod 25yと同じになります。これを 1 回、2 回、または 3 回繰り返すと、それぞれ 17、11、または 9 ビットの数値になります。これらのサイズの 1 つは、ルックアップ テーブルを作成するのに十分小さい場合があります。xy

%ただし、これが組み込みの演算子よりも高速になるとは思えません。

于 2009-06-11T12:11:43.590 に答える
0

アイデアはこちら

static int table0[256];
static int table1[256];
static int table2[256];
static int table3[256];

// ran just once to initialize the tables
void initialMod25Tables() {
    for (int i = 0; i < 256; ++i) {
        table0[i] = i % 25;
    }
    for (int i = 0; i < 256; ++i) {
        table1[i] = (i << 8) % 25;
    }
    for (int i = 0; i < 256; ++i) {
        table2[i] = (i << 16) % 25;
    }
    for (int i = 0; i < 256; ++i) {
        table3[i] = (i << 24) % 25;
    }
}

int mod25(int x) {
    int y = table0[x & 0xFF];
    x >>= 8;
    y += table1[x & 0xFF];
    x >>= 8;
    y += table2[x & 0xFF];
    x >>= 8;
    y += table3[x & 0xFF];
    y = table0[y];
    return y;
}
于 2010-07-14T12:33:04.650 に答える
0

どうですか:

int y = 0, x = (x & 0x7f); 
while (x > 25) { x -= 25; y++; }

更新: それはかなり間違っています :) しかし、アイデアはそこにあります。

于 2009-06-11T12:44:11.483 に答える
-1

数値 25 のみを考慮している場合は、整数の最後の 2 桁が 00、25、50、または 75 である場合に限り、25 で整数を除算するという事実を使用できます。したがって、モジュロを取得するには、最後の 2 桁を考慮し、次に、00、25、50、または 75 の最も近いものを引きます。

于 2009-06-11T12:12:09.220 に答える