30

Bit Twiddling hacksの乗算とルックアップを使用して O(lg(N)) 操作で N ビット整数の対数底 2 を検索するというエントリを見ています。

そのエントリの 2 番目のアルゴリズムがどのように機能するかを簡単に確認できます

static const int MultiplyDeBruijnBitPosition2[32] = 
{
  0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
  31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition2[(uint32_t)(v * 0x077CB531U) >> 27];

n = log2 vこれは が 2 の累乗であることがわかっている場所を計算vします。この場合0x077CB531は通常の De Bruijn 列であり、残りは明らかです。

ただし、そのエントリの最初のアルゴリズム

static const int MultiplyDeBruijnBitPosition[32] =
{
  0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
  8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
};

v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;

r = MultiplyDeBruijnBitPosition[(uint32_t)(v * 0x07C4ACDDU) >> 27];

私にはもう少しトリッキーに見えます。v最も近い大きな値にスナップすることから始め2^n - 1ます。次に、この2^n - 1値に を掛けます0x07C4ACDD。この場合、前のアルゴリズムの DeBruijn シーケンスと同じように機能します。

私の質問は、この魔法のシ​​ーケンスをどのように構築するの0x07C4ACDDですか? つまり、値を乗算したときに一意のインデックスを生成するために使用できるシーケンスをどのように構築するの2^n - 1でしょうか? 乗数について2^nは、上記でわかるように、単なる通常の De Bruijn シーケンスであるため、どこ0x077CB531から来たのかは明らかです。しかし、2^n - 1乗数は0x07C4ACDDどうですか?ここで明らかな何かが欠けているように感じます。

PS私の質問を明確にするために:これらのシーケンスを生成するアルゴリズムを実際に探しているわけではありません。私は、多かれ少なかれ些細なプロパティ (存在する場合) にもっと興味があります0x07C4ACDD。それ0x077CB531を機能させるプロパティは非常に明白です.1ビットステッピングでシーケンスに「保存」されたすべての5ビットの組み合わせが含まれています(これは基本的にDe Bruijnシーケンスです)。

一方0x07C4ACDD、 はそれ自体では De Bruijn シーケンスではありません。では、構築時にどのようなプロパティを目指していたのでしょうか0x07C4ACDD(非構築的な「上記のアルゴリズムを機能させる必要がある」以外に)? 誰かがどういうわけか上記のアルゴリズムを思いつきました。したがって、彼らはおそらく、このアプローチが実行可能であり、適切なシーケンスが存在することを知っていました. 彼らはどうやってそれを知ったのですか?

たとえば、任意の のアルゴリズムを構築する場合は、次のようにvします。

v |= v >> 1;
v |= v >> 2;
...

最初。次に、2 のべき乗に変換するだけです++v(vオーバーフローしないと仮定しましょう)。次に、最初のアルゴリズムを適用します。--rそして最後に、最終的な答えを得るためにやります。ただし、これらの人々はそれを最適化することに成功しました。乗数を変更してテーブルを再配置するだけで、先頭++vと末尾のステップを削除しました。--r彼らはそれが可能であることをどのように知ったのですか? この最適化の背後にある計算は何ですか?

4

3 に答える 3

16

順序nからkの記号(およびk ^ nの長さ)のDe Bruijnシーケンスには、すべての可能なn長の単語が連続した文字として表示されるという特性があります。たとえば、k = 2、n = 2の場合、可能な単語は00、01、10、11であり、De Bruijnシーケンスは0011です。00、01、11がその中に表示され、10はラッピングされます。このプロパティは、当然、De Bruijnシーケンスを左シフト(2の累乗で乗算)し、その上位nビットを取得すると、2乗の乗数ごとに一意の数値が得られることを意味します。次に、ルックアップテーブルがどれであるかを判断するために必要なだけです。これは、2の累乗より1小さい数と同様の原理で機能しますが、この場合のマジックナンバーは、De Bruijnシーケンスではなく、アナログです。定義するプロパティは単に「

De Bruijn数の構築の1つの可能な方法は、De Bruijnグラフのハミルトンパスの生成です。ウィキペディアは、そのようなグラフの例を提供します。この場合、ノードは2 ^ 5 = 32ビット整数であり、有向エッジはノード間の遷移です。遷移は左シフトであり、エッジのラベル(0または1)に応じたバイナリまたは演算です。 2 ^ n-1タイプのマジックナンバーに直接類似しているので、調べる価値があるかもしれませんが、これは人々が通常そのようなアルゴリズムを構築する方法ではありません。

実際には、特に少し異なる方法で動作させたい場合は、別の方法で構築しようとする場合があります。たとえば、ビットをいじるハックページでのゼロの先頭/末尾の数のアルゴリズムの実装は、[0..31]の値のみを返すことができます。32個のゼロがある0の場合は、追加のチェックが必要です。このチェックには分岐が必要であり、一部のCPUでは遅すぎる可能性があります。

私のやり方では、32ではなく64要素のルックアップテーブルを使用してランダムなマジックナンバーを生成し、それぞれについて2つの入力の累乗でルックアップテーブルを作成し、その正確性(単射)をチェックしてから、すべての32ビット数。正しいマジックナンバーに出会うまで続けました。結果として得られる数字は、33の可能な入力すべてに固有の、33の数字のみが表示されるため、「すべての可能なn長の単語が表示される」という特性を満たしません。

徹底的なブルートフォース検索は、特に優れた魔法数がまれな場合は遅く聞こえますが、入力として2つの値の既知の累乗を最初にテストすると、テーブルはすぐにいっぱいになり、拒否は速く、拒否率は非常に高くなります。各マジックナンバーの後でテーブルをクリアする必要があるだけです。本質的に、私はマジックナンバーを構築するために高い棄却率アルゴリズムを利用しました。

結果として得られるアルゴリズムは次のとおりです。

int32 Integer::numberOfLeadingZeros (int32 x)
{
    static int32 v[64] = {
        32, -1, 1, 19, -1, -1, -1, 27, -1, 24, 3, -1, 29, -1, 9, -1,
        12, 7, -1, 20, -1, -1, 4, 30, 10, -1, 21, -1, 5, 31, -1, -1,
        -1, -1, 0, 18, 17, 16, -1, -1, 15, -1, -1, -1, 26, -1, 14, -1,
        23, -1, 2, -1, -1, 28, 25, -1, -1, 13, 8, -1, -1, 11, 22, 6};
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    x *= 0x749c0b5d;
    return v[cast<uint32>(x) >> 26];
}

int32 Integer::numberOfTrailingZeros (int32 x)
{
    static int32 v[64] = {
        32, -1, 2, -1, 3, -1, -1, -1, -1, 4, -1, 17, 13, -1, -1, 7,
        0, -1, -1, 5, -1, -1, 27, 18, 29, 14, 24, -1, -1, 20, 8, -1,
        31, 1, -1, -1, -1, 16, 12, 6, -1, -1, -1, 26, 28, 23, 19, -1,
        30, -1, 15, 11, -1, 25, 22, -1, -1, 10, -1, 21, 9, -1, -1, -1};
    x &= -x;
    x *= 0x4279976b;
    return v[cast<uint32>(x) >> 26];
}

彼らがどうやって知ったのかというあなたの質問に関しては、彼らはおそらく知らなかったでしょう。私と同じように、彼らは実験し、物事を変えようとしました。結局のところ、マジックナンバーとルックアップテーブルが異なる2 ^ n入力の代わりに、2^n-1入力が機能する可能性があることは想像の域を超えていません。

ここでは、マジックナンバージェネレーターコードの簡略版を作成しました。2つの入力の累乗のみをチェックすると、5分ですべての可能なマジックナンバーをチェックし、1024のマジックナンバーを見つけます。とにかく2^n-1形式に縮小されるため、他の入力に対するチェックは無意味です。テーブルを作成しませんが、魔法の数がわかれば簡単です。

#include <Frigo/all>
#include <Frigo/all.cpp>

using namespace Frigo::Lang;
using namespace std;

class MagicNumberGenerator
{

    public:

        static const int32 log2n = 5;
        static const int32 n = 1 << log2n;
        static const bool tryZero = false;

        MagicNumberGenerator () {}

        void tryAllMagic ()
        {
            for( int32 magic = 0; magic < Integer::MAX_VALUE; magic++ ){
                tryMagic(magic);
            }
            tryMagic(Integer::MAX_VALUE);
            for( int32 magic = Integer::MIN_VALUE; magic < 0; magic++ ){
                tryMagic(magic);
            }
        }

        bool tryMagic (int32 magic)
        {
            //  clear table
            for( int32 i = 0; i < n; i++ ){
                table[i] = -1;
            }
            //  try for zero
            if( tryZero and not tryInput(magic, 0) ){
                return false;
            }
            //  try for all power of two inputs, filling table quickly in the process
            for( int32 i = 0; i < 32; i++ ){
                if( not tryInput(magic, 1 << i) ){
                    return false;
                }
            }
            //  here we would test all possible 32-bit inputs except zero, but it is pointless due to the reduction to 2^n-1 form
            //  we found a magic number
            cout << "Magic number found: 0x" << Integer::toHexString(magic) << endl;
            return true;
        }

        bool tryInput (int32 magic, int32 x)
        {
            //  calculate good answer
            int32 leadingZeros = goodNumberOfLeadingZeros(x);
            //  calculate scrambled but hopefully injective answer
            x |= x >> 1;
            x |= x >> 2;
            x |= x >> 4;
            x |= x >> 8;
            x |= x >> 16;
            x *= magic;
            x = Integer::unsignedRightShift(x, 32 - log2n);
            //  reject if answer is not injective
            if( table[x] != -1 ){
                return table[x] == leadingZeros;
            }
            //  store result for further injectivity checks
            table[x] = leadingZeros;
            return true;
        }

        static int32 goodNumberOfLeadingZeros (int32 x)
        {
            int32 r = 32;
            if( cast<uint32>(x) & 0xffff0000 ){
                x >>= 16;
                r -= 16;
            }
            if( x & 0xff00 ){
                x >>= 8;
                r -= 8;
            }
            if( x & 0xf0 ){
                x >>= 4;
                r -= 4;
            }
            if( x & 0xc ){
                x >>= 2;
                r -= 2;
            }
            if( x & 0x2 ){
                x >>= 1;
                r--;
            }
            if( x & 0x1 ){
                r--;
            }
            return r;
        }

        int32 table[n];

};

int32 main (int32 argc, char* argv[])
{
    if(argc||argv){}
    measure{
        MagicNumberGenerator gen;
        gen.tryAllMagic();
    }
}
于 2011-09-10T02:45:39.833 に答える
3

これは、論文Using de Bruijn Sequences to Index a 1 in a Computer Word に基づいています。にマップする完全なハッシュ関数を検索したと推測し2^n-1ます[0..31]。彼らは、乗数を段階的に構築することを含む、最大 2 ビットのセットで整数のゼロをカウントする方法を説明しています。

于 2011-09-10T02:18:18.397 に答える
3

から: http://www.stmintz.com/ccc/index.php?id=306404

130329821
0x07C4ACDD
00000111110001001010110011011101B

bit 31 - bit 27   00000  0
bit 30 - bit 26   00001  1
bit 29 - bit 25   00011  3
bit 28 - bit 24   00111  7
bit 27 - bit 23   01111 15
bit 26 - bit 22   11111 31
bit 25 - bit 21   11110 30
bit 24 - bit 20   11100 28
bit 23 - bit 19   11000 24
bit 22 - bit 18   10001 17
bit 21 - bit 17   00010  2
bit 20 - bit 16   00100  4
bit 19 - bit 15   01001  9
bit 18 - bit 14   10010 18
bit 17 - bit 13   00101  5
bit 16 - bit 12   01010 10
bit 15 - bit 11   10101 21
bit 14 - bit 10   01011 11
bit 13 - bit  9   10110 22
bit 12 - bit  8   01100 12
bit 11 - bit  7   11001 25
bit 10 - bit  6   10011 19
bit  9 - bit  5   00110  6
bit  8 - bit  4   01101 13
bit  7 - bit  3   11011 27
bit  6 - bit  2   10111 23
bit  5 - bit  1   01110 14
bit  4 - bit  0   11101 29
bit  3 - bit 31   11010 26 
bit  2 - bit 30   10100 20
bit  1 - bit 29   01000  8
bit  0 - bit 28   10000 16

0x07C4ACDD は 5 ビットの de Bruijn シーケンスのようです。

于 2015-11-09T20:46:34.227 に答える