web-dev-qa-db-ja.com

ビットシフトを使用してモジュロを再実装しますか?

Mod演算子が非常に遅い非常に限られたシステム用のコードを書いています。私のコードでは、モジュロを1秒間に約180回使用する必要があり、モジュロを可能な限り削除すると、コードの速度が大幅に向上することがわかりました。現在、メインループの1サイクルは1/60で実行されていません。必要に応じて2番目。乗算や除算のようにビットシフトだけを使ってモジュロを再実装できるのではないかと思っていました。これがc ++でのこれまでの私のコードです(Assemblyを使用してモジュロを実行できる場合はさらに良いでしょう)。除算や乗算を使用せずにモジュロを削除するにはどうすればよいですか?

    while(input > 0)
{
    out = (out << 3) + (out << 1);
    out += input % 10;

    input = (input >> 8) + (input >> 1);
}

EDIT:実際、私は1秒間に180回以上それを行う必要があることに気づきました。入力の値として見ると、40桁までの非常に大きな数値になる可能性があります。

13
PgrAm

単純なビット演算でできることは、値(被除数)の2の累乗(除数)を除数-1とAND演算することによって取得することです。いくつかの例:

unsigned int val = 123; // initial value
unsigned int rem;

rem = val & 0x3; // remainder after value is divided by 4. 
                 // Equivalent to 'val % 4'
rem = val % 5;   // remainder after value is divided by 5.
                 // Because 5 isn't power of two, we can't simply AND it with 5-1(=4). 

なぜ機能するのですか?値123のビットパターンを1111011と考えてから、除数4のビットパターンを考えてみましょう。 00000100。今では知っているように、除数は2の累乗(4のように)である必要があり、ビットパターン00000011を生成する1だけデクリメントする必要があります(10進数で4から3)。元の123と3の両方をビット単位でANDすると、結果のビットパターンは00000011になります。それは10進数で3であることがわかります。 2の累乗の除数が必要な理由は、1ずつデクリメントすると、重要度の低いビットがすべて1に設定され、残りは0になるためです。ビット単位のANDを実行すると、元の値からより重要なビットが「キャンセル」され、元の値の余りが除数で除算されたままになります。

ただし、このような特定の何かを任意の除数に適用することは、除数を事前に知っていない限り機能しません(コンパイル時に、それでも除数固有のコードパスが必要です)-実行時に解決することは、特にあなたの場合では実行可能ではありませんパフォーマンスが重要な場合。

また、 主題に関連する前の質問 があります。これはおそらく、さまざまな観点から問題に関する興味深い情報を持っています。

15
zxcdw

実際、定数による除算はコンパイラーにとってよく知られた最適化であり、実際、gccはすでにそれを行っています。

この単純なコードスニペット:

int mod(int val) {
   return val % 10;
}

-O3を使用してかなり古いgccで次のコードを生成します。

_mod:
        Push    ebp
        mov     edx, 1717986919
        mov     ebp, esp
        mov     ecx, DWORD PTR [ebp+8]
        pop     ebp
        mov     eax, ecx
        imul    edx
        mov     eax, ecx
        sar     eax, 31
        sar     edx, 2
        sub     edx, eax
        lea     eax, [edx+edx*4]
        mov     edx, ecx
        add     eax, eax
        sub     edx, eax
        mov     eax, edx
        ret

関数エピローグ/プロローグを無視すると、基本的に2つのmul(x86では幸運で1つにleaを使用できます)といくつかのシフトと追加/サブがあります。この最適化の背後にある理論をどこかですでに説明したことを知っているので、もう一度説明する前に、その投稿を見つけることができるかどうかを確認します。

現在、メモリへのアクセスよりも確かに高速な最新のCPUでは(キャッシュにヒットした場合でも)、明らかにもう少し古いCPUの方が高速かどうかは、ベンチマークでのみ答えることができる質問です(また、コンパイラが実行していることを確認してください)。その最適化、そうでなければ、いつでもここでgccバージョンを「盗む」ことができます;))。特に、効率を上げるには、効率的なマルチ(つまり、乗算命令の上位ビット)に依存することを考慮してください。このコードはnotサイズに依存しない-正確にはマジックナンバーの変更(そしておそらく追加/シフトの一部)ですが、それは適応可能であることに注意してください。

3
Voo

ビットシフトは本質的にバイナリであるため(今日実行する予定のマシンでは)、ビットシフトを使用してモジュロ10を実行するのは困難で醜いものになります。あなたがそれについて考えるならば、ビットシフトは単に2で乗算または除算することです。

しかし、ここで行うことができる明らかな時空取引があります。outout % 10の値のテーブルを設定し、それを調べます。すると線は

  out += tab[out]

運が良ければ、それは1つの16ビット加算とストア操作であることがわかります。

2
Charlie Martin

モジュロ10とシフトを実行したい場合は、ダブルダブルアルゴリズムをニーズに適合させることができますか?

このアルゴリズムは、モジュロまたは除算を使用せずに2進数を10進数に変換するために使用されます。

1
Rafał Rawicki

16の累乗はすべて6で終わります。数値を16の累乗の合計として表す場合(つまり、ニブルに分割する場合)、各項は、1の位を除いて、同じ方法で最後の桁に寄与します。

0x481A % 10 = ( 0x4 * 6 + 0x8 * 6 + 0x1 * 6 + 0xA ) % 10

6 = 5 + 1であり、偶数の場合は5がキャンセルされることに注意してください。したがって、ニブル(最後のものを除く)を合計し、結果が奇数の場合は5を加算します。

0x481A % 10 = ( 0x4 + 0x8 + 0x1 /* sum = 13 */
                + 5 /* so add 5 */ + 0xA /* and the one's place */ ) % 10
            = 28 % 10

これにより、16ビットの4ニブルモジュロが最大で0xF * 4 + 5 = 65の数値に削減されます。バイナリでは、それは厄介なことにまだ3ニブルなので、アルゴリズムを繰り返す必要があります(ただし、そのうちの1つは実際にはカウントされません)。

ただし、286には、合計を実行して1回のパスで結果を取得するために使用できる適度に効率的なBCD加算が必要です。 (これには、各ニブルを手動でBCDに変換する必要があります。プラットフォームについて、それを最適化する方法や問題があるかどうかを説明するのに十分な知識がありません。)

1
Potatoswatter