Bit twiddling:设置了哪个位?

时间:2010-08-12 06:03:20

标签: c bit-manipulation

我有一个64位无符号整数,正好设置了1位。我想为每个可能的64个值分配一个值(在这种情况下,奇数素数,所以0x1对应3,0x2对应5,...,0x8000000000000000对应313)。

似乎最好的方法是转换1 - > 0,2 - > 1,4-> 2,8-> 3,...,2 ^ 63 - > 63并查找数组中的值。但即使如此,我也不确定获得二进制指数的最快方法是什么。并且可能还有更快/更好的方式。

此操作将使用10 14 至10 16 次,因此性能是一个严重的问题。

15 个答案:

答案 0 :(得分:39)

最后一个最佳解决方案。请参阅本节末尾,了解如果输入保证只有一个非零位,该怎么办: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位输入算法;否则,只需添加一个条件以查看该位是在高32位还是低32位,然后在此处使用32位算法。

更新:这是我自己开发的至少一个64位版本,但它使用了除法(实际上是模数)。

r = Table[v%67];

对于2的每个幂,v%67具有不同的值,所以只需将奇数素数(或位索引,如果你不想要奇数素数)放在表中的正确位置。不使用3个位置(0,17和34),如果您还想接受所有位零作为输入,这可能很方便。

更新2:64位版本。

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

这是我的原创作品,但是我从De Bruijn sequence获得了B(2,6) this chess site,所以除了弄清楚De Bruijn序列是什么以及使用Google之外,我无法理解任何事情。 。 ; - )

关于其工作原理的一些补充说明:

幻数是B(2,6) De Bruijn序列。它的特性是,如果你看一个6连续的位窗口,你可以通过适当地旋转数字来获得该窗口中的任何六位值,并且通过恰好一次旋转获得每个可能的六位值。

我们将有问题的窗口固定为前6位位置,并选择前6位中带有0的De Bruijn序列。这使得我们永远不必处理位旋转,只需要移位,因为0将自然地进入底部位(并且我们永远不会在最高6位窗口中从底部查看超过5位)

现在,此函数的输入值是2的幂。因此,将De Bruijn序列乘以输入值会执行log2(value)位的位移。我们现在在高6位中有一个数字,该数字唯一地确定我们移位了多少位,并且可以将其用作表的索引以获得移位的实际长度。

只要您愿意实现乘法,这种方法可以用于任意大或任意小的整数。您只需找到B(2,k) De Bruijn序列,其中k是位数。我上面提供的国际象棋wiki链接的值为k的De Bruijn序列范围从1到6,而一些快速的Google搜索显示有一些关于在一般情况下生成它们的最佳算法的论文。

答案 1 :(得分:31)

如果性能是一个严重的问题,那么你应该使用intrinsics / builtins来使用CPU特定的指令,例如这里为gcc找到的指令:

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

- 内置功能:int __builtin_ffs (unsigned int x) 返回一个加上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个版本。

答案 2 :(得分: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;
}

这比循环已经展开的循环更有优势。但是,如果这对性能至关重要,那么您将需要测试和测量每个提议的解决方案。

答案 3 :(得分:6)

有些架构(实际上是一个惊人的数字)只有一条指令可以进行你想要的计算。在ARM上,它将是CLZ(计数前导零)指令。对于intel,BSF(位扫描前向)或BSR(位扫描反向)指令可以帮助你。

我想这不是一个 C 的答案,但它会为你提供所需的速度!

答案 4 :(得分:2)

  • 预先计算1<< i(对于i = 0..63)并将它们存储在数组中
  • 使用二进制搜索来查找给定值
  • 的数组的索引
  • 使用此索引
  • 在另一个数组中查找素数

与我在此处发布的其他答案相比,这应该只需要6个步骤来查找索引(而不是最多64个)。但是我不清楚这个答案的一个步骤是否比仅仅移位和递增计数器更耗时。你可能想试试这两个。

答案 5 :(得分:2)

由于速度,大概不是内存使用,很重要,这是一个疯狂的想法:

w1 =前16位
w2 =第二个16位
w3 =第3位16位
w4 =第4位16位

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

其中array1..4是稀疏填充的64K数组,包含实际的素数值(在与位位置不对应的位置为零)

答案 6 :(得分: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))

答案 7 :(得分:1)

如果不使用汇编或特定于编译器的扩展来查找已设置的第一个/最后一个位,则最快的算法是二进制搜索。首先检查是否设置了前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;
}
假设{p> valuint64_t,但为了优化32位计算机的这种情况,您应该特别考虑第一次检查,然后使用32位{{1}执行循环变量。

答案 8 :(得分:1)

参见http://graphics.stanford.edu/~seander/bithacks.html - 特别是“查找整数的整数对数基数2(也就是最高位集的位置)” - 对于某些替代算法。 (如果你对速度非常认真,如果你的CPU有专门的指令,你可以考虑放弃C。)

答案 9 :(得分:1)

调用glibc中的GNU POSIX扩展函数ffsll。如果该功能不存在,请退回__builtin_ffsll。两个函数都返回第一个位集的index + 1,或者为零。使用Visual-C ++,您可以使用_BitScanForward64

答案 10 :(得分:0)

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

然后按照你所说的基于bit_position查找素数。

答案 11 :(得分:0)

可能发现log(n)/ log(2)在合理的时间范围内为你提供了0,1,2 ......否则,某种形式的基于散列表的方法可能会有用。

答案 12 :(得分:0)

假设IEEE float的另一个答案:

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

它的工作方式与您要求的输入值(确切地设置为1位)相同,并且对其他值也有用(尝试确切地知道该行为是什么)。不知道它是快还是慢;这可能取决于你的机器和编译器。

答案 13 :(得分:0)

来自GnuChess来源:

unsigned char leadz (BitBoard b)
/**************************************************************************
 *
 *  Returns the leading bit in a bitboard.  Leftmost bit is 0 and
 *  rightmost bit is 63.  Thanks to Robert Hyatt for this algorithm.
 *
 ***************************************************************************/
{
  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%的操作。

答案 14 :(得分:0)

这是针对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