31


22

ビット調整:どのビットが設定されていますか?

正確に1ビットが設定された64ビットの符号なし整数があります。 可能な64個の値のそれぞれに値を割り当てたいと思います(この場合、奇数の素数は0x1が3に対応し、0x2は5に対応し、…​、0x8000000000000000は313に対応します)。

1→ 0、2→ 1、4→ 2、8→ 3、…​、2 ^ 63→ 63を変換し、配列内の値を検索するのが最良の方法のようです。 しかし、そうだとしても、バイナリ指数に到達する最速の方法が何であるかはわかりません。 そして、より高速/より良い方法がまだあるかもしれません。

この操作は10 ^ 14 ^から10 ^ 16 ^回使用されるため、パフォーマンスは深刻な問題です。

14 Answer


39


最後に最適なソリューション。 入力にゼロ以外のビットが1つだけあることが保証されている場合の対処方法については、このセクションの終わりを参照してください:http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn

これがコードです:

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];

64ビット入力の直接乗算ベースのアルゴリズムにこれを適応できる場合があります。そうでない場合は、1つの条件を追加して、ビットが上位32ポジションにあるか下位32ポジションにあるかを確認し、ここで32ビットアルゴリズムを使用します。

更新:これは、私が自分で開発した64ビットバージョンの少なくとも1つですが、除算(実際にはモジュロ)を使用しています。

r = Table[v%67];

2のべき乗ごとに、 `v%67`には異なる値があるため、奇数の素数(または奇数の素数が必要ない場合はビットインデックス)をテーブルの正しい位置に配置してください。 3桁(0、17、および34)は使用されません。これは、入力としてall-bits-zeroも受け入れたい場合に便利です。

アップデート2:64ビットバージョン

r = Table[(uint64_t)(val * 0x022fdd63cc95386dull) >> 58];

これは私の元の作品ですが、http://chessprogramming.wikispaces.com/De+Bruijnから B(2,6) De Bruijn sequenceを取得しました+ sequence [このチェスサイト]ですので、De Bruijnのシーケンスが何であるかを把握し、Googleを使用する以外は何も信用できません。 ;-)

これがどのように機能するかについての追加のコメント:

マジックナンバーは「B(2,6)」De Bruijnシーケンスです。 6連続ビットウィンドウを見ると、適切に数を回転させることでそのウィンドウ内の6ビット値を取得でき、可能な6ビット値はそれぞれ1回の回転で取得できるという特性があります。

問題のウィンドウを上位6ビット位置に修正し、上位6ビットに0があるDe Bruijnシーケンスを選択します。 これにより、ビットの回転を処理する必要がなくなり、シフトのみが行われます。0は自然に最下位ビットに到達するためです(そして、最上部から6ビットのウィンドウで最下位から5ビット以上を見ることができません) 。

現在、この関数の入力値は2の累乗です。 したがって、De Bruijnシーケンスに入力値を乗算すると、 `log2(value)`ビットによるビットシフトが実行されます。 上位6ビットには、シフトしたビット数を一意に決定する数値があり、それをテーブルへのインデックスとして使用して、実際のシフト長を取得できます。

この同じアプローチは、乗算を実装する意思がある限り、任意の大きな整数または任意の小さな整数に使用できます。 「B(2、k)」De Bruijnシーケンスを見つける必要があります。ここで、「k」はビット数です。 上記で提供したチェスwikiリンクには、1から6の範囲の「k」の値に対するDe Bruijnシーケンスがあり、いくつかのクイックグーグルショーは、一般的な場合にそれらを生成するための最適なアルゴリズムに関するいくつかの論文があります。


31


パフォーマンスが深刻な問題である場合は、組み込み関数/組み込み関数を使用して、gccのここにあるようなCPU固有の命令を使用する必要があります。

—組み込み関数: int __builtin_ffs(unsigned int x) xの最下位1ビットのインデックスに1を加えた値を返します。xがゼロの場合はゼロを返します。

—組み込み関数: `int __builtin_clz(unsigned int x)`最上位ビット位置から始まるxの先頭の0ビットの数を返します。 xが0の場合、結果は未定義です。

—組み込み関数: `int __builtin_ctz(unsigned int x)`最下位ビット位置から始まるxの末尾の0ビットの数を返します。 xが0の場合、結果は未定義です。

このようなことは、ビットアレイによって示される最初の空でないキューを見つける必要があるカーネルスケジューラなど、多くのO(1)アルゴリズムの中核です。

注:「unsigned int」バージョンをリストしましたが、gccには「unsigned long long」バージョンもあります。


14


バイナリ検索手法を使用できます。

int pos = 0;
if ((value & 0xffffffff) == 0) {
    pos += 32;
    value >>= 32;
}
if ((value & 0xffff) == 0) {
    pos += 16;
    value >>= 16;
}
if ((value & 0xff) == 0) {
    pos += 8;
    value >>= 8;
}
if ((value & 0xf) == 0) {
    pos += 4;
    value >>= 4;
}
if ((value & 0x3) == 0) {
    pos += 2;
    value >>= 2;
}
if ((value & 0x1) == 0) {
    pos += 1;
}

これには、ループが既に展開されているというループよりも利点があります。 ただし、これが本当にパフォーマンスに重要な場合は、提案されたすべてのソリューションをテストおよび測定する必要があります。


6


一部のアーキテクチャ(実際には驚くべき数)には、必要な計算を実行できる単一の命令があります。 ARMでは、 CLZ(先行ゼロのカウント)命令になります。 Intelの場合、「BSF」(ビットスキャンフォワード)または「BSR」(ビットスキャンリバース)命令が役立ちます。

これは本当に_C_の答えではありませんが、必要な速度が得られると思います!


2


  • 1 << i(i = 0..63の場合)を事前計算し、配列に格納します

  • バイナリ検索を使用して、指定された値の配列へのインデックスを見つけます

  • このインデックスを使用して別の配列の素数を検索します

私がここに投稿した他の回答と比較すると、これはインデックスを見つけるために6ステップしか取らないはずです(最大64に対して)。 しかし、この答えの1つのステップが、ビットシフトとカウンタのインクリメントよりも時間のかかるものではないかどうかはわかりません。 ただし、両方試してみることもできます。


2


おそらくメモリ使用量ではない速度が重要なので、これはおかしいアイデアです:

w1 = 1番目の16ビット+ w2 = 2番目の16ビット+ w3 = 3番目の16ビット+ w4 = 4番目の16ビット

結果= array1 [w1] + array2 [w2] + array3 [w3] + array4 [w4]

ここで、array1..4は、実際の素数値(およびビット位置に対応しない位置のゼロ)を含むまばらに配置された64K配列です。


2


@Rsソリューションは優れています。これは、64ビットバリアントであり、テーブルは既に計算されています…​

static inline unsigned char bit_offset(unsigned long long self) {
    static const unsigned char mapping[64] = {
        [0]=0,   [1]=1,   [2]=2,   [4]=3,   [8]=4,   [17]=5,  [34]=6,  [5]=7,
        [11]=8,  [23]=9,  [47]=10, [31]=11, [63]=12, [62]=13, [61]=14, [59]=15,
        [55]=16, [46]=17, [29]=18, [58]=19, [53]=20, [43]=21, [22]=22, [44]=23,
        [24]=24, [49]=25, [35]=26, [7]=27,  [15]=28, [30]=29, [60]=30, [57]=31,
        [51]=32, [38]=33, [12]=34, [25]=35, [50]=36, [36]=37, [9]=38,  [18]=39,
        [37]=40, [10]=41, [21]=42, [42]=43, [20]=44, [41]=45, [19]=46, [39]=47,
        [14]=48, [28]=49, [56]=50, [48]=51, [33]=52, [3]=53,  [6]=54,  [13]=55,
        [27]=56, [54]=57, [45]=58, [26]=59, [52]=60, [40]=61, [16]=62, [32]=63
    };
    return mapping[((self & -self) * 0x022FDD63CC95386DULL) >> 58];
}

提供されたマスクを使用してテーブルを作成しました。

>>> ', '.join('[{0}]={1}'.format(((2**bit * 0x022fdd63cc95386d) % 2**64) >> 58, bit) for bit in xrange(64))
'[0]=0, [1]=1, [2]=2, [4]=3, [8]=4, [17]=5, [34]=6, [5]=7, [11]=8, [23]=9, [47]=10, [31]=11, [63]=12, [62]=13, [61]=14, [59]=15, [55]=16, [46]=17, [29]=18, [58]=19, [53]=20, [43]=21, [22]=22, [44]=23, [24]=24, [49]=25, [35]=26, [7]=27, [15]=28, [30]=29, [60]=30, [57]=31, [51]=32, [38]=33, [12]=34, [25]=35, [50]=36, [36]=37, [9]=38, [18]=39, [37]=40, [10]=41, [21]=42, [42]=43, [20]=44, [41]=45, [19]=46, [39]=47, [14]=48, [28]=49, [56]=50, [48]=51, [33]=52, [3]=53, [6]=54, [13]=55, [27]=56, [54]=57, [45]=58, [26]=59, [52]=60, [40]=61, [16]=62, [32]=63'

コンパイラが文句を言う必要があります:

>>> ', '.join(map(str, {((2**bit * 0x022fdd63cc95386d) % 2**64) >> 58: bit for bit in xrange(64)}.values()))
'0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12'

^^は、ソートされたキーを反復処理することを前提としていますが、将来的にはそうではないかもしれません…​

unsigned char bit_offset(unsigned long long self) {
    static const unsigned char table[64] = {
        0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48,
        28, 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49,
        18, 29, 11, 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43,
        21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50,
        31, 19, 15, 30, 14, 13, 12
    };
    return table[((self & -self) * 0x022FDD63CC95386DULL) >> 58];
}

簡単なテスト:

>>> table = {((2**bit * 0x022fdd63cc95386d) % 2**64) >> 58: bit for bit in xrange(64)}.values()
>>> assert all(i == table[(2**i * 0x022fdd63cc95386d % 2**64) >> 58] for i in xrange(64))


1


設定されている最初/最後のビットを見つけるためにアセンブリまたはコンパイラ固有の拡張機能を使用する以外に、最速のアルゴリズムはバイナリ検索です。 最初の32ビットのいずれかが設定されているかどうかを最初に確認します。 その場合、最初の16個のいずれかが設定されているかどうかを確認してください。 もしそうなら、最初の8つのいずれかが設定されているかどうかを確認してください。 Etc. これを行う関数は、検索の各リーフで奇数の素数を直接返すか、奇数の素数のテーブルへの配列インデックスとして使用するビットインデックスを返すことができます。

バイナリ検索のループ実装は次のとおりです。最適化されていると判断された場合、コンパイラは確実に展開できます。

uint32_t mask=0xffffffff;
int pos=0, shift=32, i;
for (i=6; i; i--) {
    if (!(val&mask)) {
        val>>=shift;
        pos+=shift;
    }
    shift>>=1;
    mask>>=shift;
}

val`は uint64_t`と見なされますが、これを32ビットマシン向けに最適化するには、最初のチェックを特殊なケースにしてから、32ビットの `val`変数でループを実行する必要があります。


1


代替アルゴリズムについては、http://graphics.stanford.edu/~seander/bithacks.html-具体的には「整数の整数ログベース2(別名、最上位ビットセットの位置)を見つける」を参照してください。 (速度について本当に真剣に取り組んでいる場合、CPUに専用の命令がある場合は、Cを捨てることを検討してください)。


1


glibcにあるGNU POSIX拡張関数http://linux.die.net/man/3/ffsll [ffsll]を呼び出します。 関数が存在しない場合は、http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Other-Builtins.html [__builtin_ffsll]に戻ってください。 両方の関数は、最初のビットセットの「インデックス+ 1」、またはゼロを返します。 Visual-C ++では、http://msdn.microsoft.com/en-us/library/wfd9z0bb(VS.80).aspx [_BitScanForward64]を使用できます。