web-dev-qa-db-ja.com

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

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

1-> 0、2-> 1、4-> 2、8-> 3、...、2 ^ 63-> 63を変換し、配列内の値を検索するのが最善の方法のようです。しかし、そうだとしても、2進指数を取得するための最速の方法が何であるかはわかりません。そして、まだもっと速い/より良い方法があるかもしれません。

この操作が使用されます1014 10まで16 そのため、パフォーマンスは深刻な問題です。

31
Charles

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

http://gcc.gnu.org/onlinedocs/gcc-4.5.0/gcc/Other-Builtins.html

—組み込み関数:int __builtin_ffs (unsigned int x) 1とxの最下位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バージョンがあります。

31
Evan Teran

最後に、最適なソリューション。入力にゼロ以外のビットが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)は使用されません。これは、入力として全ビットゼロも受け入れる場合に便利です。

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

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

これは私のオリジナルの作品ですが、B(2,6)De Bruijn sequence from this chess site を取得したので、理解する以外に何も信用できませんDe Bruijnシーケンスとは何か、そしてGoogleを使用しています。 ;-)

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

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

問題のウィンドウを上位6ビットの位置に修正し、上位6ビットに0が含まれるDeBruijnシーケンスを選択します。これにより、ビットのローテーションを処理する必要がなくなり、シフトのみを処理する必要がなくなります。これは、0が自然に下のビットに入るので(そして、上から6ビットのウィンドウで下から5ビット以上を見ることになりません)。 。

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

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

39
R..

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

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

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

14
ggg

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

これは実際には[〜#〜] c [〜#〜]の答えではないと思いますが、必要な速度が得られます。

6
Carl Norum

おそらくメモリ使用量ではなく速度が重要なので、ここにクレイジーなアイデアがあります。

w1 =最初の16ビット
w2 = 2番目の16ビット
w3 = 3番目の16ビット
w4 = 4番目の16ビット

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

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

2
Andrew

@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))
2
Samy Vilar
  • 1 << i(i = 0..63の場合)を事前計算し、それらを配列に格納します
  • バイナリ検索を使用して、指定された値の配列へのインデックスを見つけます
  • このインデックスを使用して、別の配列の素数を検索します

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

2
Andre Holzner

GlibcにあるGNU POSIX拡張関数 ffsll を呼び出します。関数が存在しない場合は、 __builtin_ffsll 。どちらの関数も、最初に設定されたビットのindex + 1、つまりゼロを返します。Visual-C++では、 _ BitScanForward64 を使用できます。

1
Matt Joiner

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

これがバイナリ検索のループ実装です。これが最適であると見なされた場合、コンパイラは確実に展開できます。

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

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

1
R..

http://graphics.stanford.edu/~seander/bithacks.html -具体的には「整数の2を底とする整数の対数(別名、最上位ビットセットの位置)の検索」を参照してください。アルゴリズム。 (速度を真剣に考えている場合、CPUに専用の命令がある場合は、Cを破棄することを検討してください)。

1
Tony Delroy

これは32ビットのJava用ですが、64ビットに適応できるはずです。分岐がないため、これが最速の原因になると想定しています。

static public final int msb(int n) {
    n |= n >>> 1;  
    n |= n >>> 2; 
    n |= n >>> 4; 
    n |= n >>> 8; 
    n |= n >>> 16; 
    n >>>= 1;
    n += 1; 
    return n;
}

static public final int msb_index(int n) {

    final int[] multiply_de_bruijn_bit_position = {
        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
    };
    return multiply_de_bruijn_bit_position[(msb(n) * 0x077CB531) >>> 27];
}

詳細は次のとおりです。 http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup

// Count the consecutive zero bits (trailing) on the right with multiply and lookup

unsigned int v;  // find the number of trailing zeros in 32-bit v 
int r;           // result goes here
static const int MultiplyDeBruijnBitPosition[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 = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];

// Converting bit vectors to indices of set bits is an example use for this. 
// It requires one more operation than the earlier one involving modulus 
// division, but the multiply may be faster. The expression (v & -v) extracts 
// the least significant 1 bit from v. The constant 0x077CB531UL is a de Bruijn 
// sequence, which produces a unique pattern of bits into the high 5 bits for 
// each possible bit position that it is multiplied against. When there are no 
// bits set, it returns 0. More information can be found by reading the paper 
// Using de Bruijn Sequences to Index 1 in a Computer Word by 
// Charles E. Leiserson, Harald Prokof, and Keith H. Randall. 

そして最後に: http://supertech.csail.mit.edu/papers/debruijn.pdf

0
clankill3r

IEEE floatを想定した別の回答:

int get_bit_index(uint64_t val)
{
    union { float f; uint32_t i; } u = { val };
    return (u.i>>23)-127;
}

要求した入力値(正確に1ビットセット)に対して指定されたとおりに機能し、他の値に対しても有用な動作をします(その動作が何であるかを正確に把握してください)。速いのか遅いのかわかりません。それはおそらくあなたのマシンとコンパイラに依存します。

0
R..
unsigned bit_position = 0;
while ((value & 1) ==0)
{
   ++bit_position;
   value >>= 1;
}

次に、あなたが言うように、bit_positionに基づいて素数を調べます。

0
Andre Holzner

あなたmay log(n)/ log(2)があなたに0、1、2、...を与えることを見つけてください。そうでなければ、何らかの形式のハッシュテーブルベースのアプローチが役立つ可能性があります。

0
Will A

GnuChessソースから:

 unsigned char Leadz(BitBoard b)
/******************************** ****************************************** 
 * 
 *ビットボードの先頭ビットを返します。左端のビットは0で
 *右端のビットは63です。このアルゴリズムを提供してくれたRobertHyattに感謝します。
 * 
 *************** ************************************************** ********** /
{
if(b >> 48)return lzArray [b >> 48]; 
 if(b >> 32) return lzArray [b >> 32] + 16; 
 if(b >> 16)return lzArray [b >> 16] + 32; 
 return lzArray [b] + 48; 
} 

ここで、lzArrayはサイズ2 ^ 16の事前生成された配列です。これにより、完全なバイナリ検索と比較して、操作の50%が節約されます。

0
Thomas Ahle