C 位旋转:设置了哪个位?

C 位旋转:设置了哪个位?,c,bit-manipulation,C,Bit Manipulation,我有一个64位无符号整数,正好是1位。我想为本例中可能的64个值中的每一个赋值,奇数素数,所以0x1对应于3,0x2对应于5,…,0x80000000000000对应于313 看起来最好的方法是转换1→ 0, 2 → 1, 4 → 2, 8 → 3, …, 263 → 63并在数组中查找值。但即使是这样,我也不确定获得二进制指数的最快方法是什么。也许还有更有效的方法 此操作将使用1014到1016次,因此性能是一个严重的问题 unsigned bit_position = 0; while ((

我有一个64位无符号整数,正好是1位。我想为本例中可能的64个值中的每一个赋值,奇数素数,所以0x1对应于3,0x2对应于5,…,0x80000000000000对应于313

看起来最好的方法是转换1→ 0, 2 → 1, 4 → 2, 8 → 3, …, 263 → 63并在数组中查找值。但即使是这样,我也不确定获得二进制指数的最快方法是什么。也许还有更有效的方法


此操作将使用1014到1016次,因此性能是一个严重的问题

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

然后根据您所说的位位置查找素数。

您可能会发现logn/log2给出了0,1,2。。。你在一个合理的时间范围内。否则,某种形式的基于哈希表的方法可能会很有用。

有些体系结构数量惊人,实际上只有一条指令可以完成您想要的计算。在ARM上,它将是CLZ count前导零指令。对于intel,BSF位扫描正向或BSR位扫描反向指令将帮助您解决此问题

我想这不是一个真正的C答案,但它会让你的速度你需要


预计算1如果性能是一个严重的问题,那么您应该使用内部函数/内置函数来使用特定于CPU的指令,例如下面针对GCC的指令:

内置函数int u内置函数ffsunsigned int x

返回1加上x的最低有效1位的索引,或者如果x为零,则返回零

内置函数int uu内置函数clzunsigned int x

返回x中前导0位的数目,从最高有效位位置开始。如果x为0,则结果未定义

内置函数int uu内置函数ctzunsigned int x

返回x中从最低有效位位置开始的尾随0位数。如果x为0,则结果未定义

这样的事情是许多O1算法的核心,比如内核调度器,它需要找到由位数组表示的第一个非空队列


注意:我列出了unsigned int版本,但是GCC也有unsigned long long版本。

您可以使用二进制搜索技术:

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

这比循环的优点是循环已经展开。但是,如果这真的是性能关键,您将需要测试和度量每个建议的解决方案。

除了使用汇编或编译器特定的扩展来查找设置的第一位/最后一位之外,最快的算法是二进制搜索。首先检查是否设置了前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;
}

val被假定为uint64_t,但要对32位机器进行优化,您应该首先进行特殊情况检查,然后使用32位val变量执行循环。

有关一些替代算法,请参见-具体查找整数的整数对数基2,即最高位集的位置。如果你真的很关心速度,如果CPU有一个专门的指令,你可以考虑开除C。< /P> < P>另一个假设IEEE浮点:


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

调用glibc中的gnuposix扩展函数。如果功能不存在,请依靠。这两个函数都返回第一个位集的索引+1或零。使用Visual C++,您可以使用。

最终找到最佳解决方案。请参阅本节末尾,了解在保证输入正好有一个非零位时应采取的措施:

代码如下:

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,如果您还希望接受所有位0作为输入,这可能很方便

更新2:64位版本

这是我的原创作品,但是我从中得到了B2,6,所以除了弄清楚什么是De Bruijn序列和使用Google之外,我什么都不能相信; -

关于如何工作的一些补充说明:

幻数是B2,6 De Bruijn序列。它的特性是,如果您查看一个连续的6位窗口,您可以通过适当旋转数字来获得该窗口中的任何6位值,并且每个可能的6位值都是通过一次旋转获得的

我们将讨论中的窗口固定为前6位位置,并选择一个在前6位中有0的De Bruijn序列。这使得我们不必处理位旋转,只需移位,因为0将自然进入底部位,并且我们永远不会在顶部6位窗口中看到底部超过5位

现在,这个函数的输入值是2的幂。因此,将De Bruijn序列与输入值相乘将执行一个位移位和log2value位。我们现在在上面的6位中有一个数字,它唯一地决定了我们移位了多少位,并且可以使用它作为表中的索引来获得移位的实际长度


同样的方法也可以用于任意大或任意小的整数,只要您愿意实现乘法。你只需要找到一个B2,k的De Bruijn序列,其中k是位数。我在上面提供的chess wiki链接中有k值从1到6的De Bruijn序列,一些快速的谷歌搜索显示,在一般情况下,有一些关于生成它们的最佳算法的论文。

来自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%的操作。

因为速度很重要,可能不是内存使用,所以这里有一个疯狂的想法:

w1=前16位 w2=第二个16位 w3=第三个16位 w4=第四个16位

结果=阵列1[w1]+阵列2[w2]+阵列3[w3]+阵列4[w4]


其中,array1..4是稀疏填充的64K数组,其中包含实际素数值,并且在与位位置不对应的位置中包含零。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))

这是针对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];
}
以下是来自以下方面的更多信息:

最后:

此操作将使用10^14到10^16次,因此性能是一个严重问题。干净利落+1仅此而已。这与最快的方法可能需要特定于CPU的指令基本相同。如果您保证只设置了一位,则可能有一种解决方案只涉及单个整数乘法或乘法和查表。我得想一想,也许其他人会先想一想。我刚刚在著名的“旋转”页面上找到了它。请看下面我的最新答案。太慢了!位的平均位置约为10,即值=1@R...:也许吧。同样,代码很小,可以放在CPU的1级缓存中,这可以使它非常快。分析是唯一可以确定的方法。执行10倍于您应该执行的循环迭代不会很快。最佳代码也可以很容易地放入一级缓存中。很好,但原始问题中没有提供关键的“平均位位置”信息,因此要考虑到这一点有点困难。@Matthew:如果以相同的概率选择64个可能的数字,这将需要大约128个时钟,这更糟糕!非常干净的实现。您还可以将其作为一个具有固定迭代次数的循环来执行,并让编译器为二进制搜索潜在地展开。也许这个问题需要uu asm{}来实现纯速度,+1用于二进制搜索,但是-1用于需要数组来执行二进制搜索的荒谬想法。你可以直接对变量进行二进制搜索,它实际上会很快。你可以将64位整数转换为浮点类型,我认为这不是一个特别便宜的操作。是的,也许是为了节省量子计算机的开销。这不仅仅是一个多贵的问题,你需要一个支持长双精度的编译器,double只有53位尾数,因此无法将64位精确转换为该类型。我相信80位长的双打有64位尾数。@Praetorian:事实上,即使是普通浮点数也可以。参数是2的精确幂,因此尾数需要零位。所需要的只是使指数适合。@R..:一个双精度的指数有11位,这足以适合64位的值。是的,这就是你应该做的。MSVC的等价物是BitScanForward64和BitScanReverse64的内部函数,还有一些不支持的系统版本
由这两个函数中的任何一个进行排序:对于不同的芯片,是否有一个页面解释哪些内部函数映射到处理器指令,哪些使用库调用?这个页面有一个特定于目标的内部函数列表。我假设我在响应中放置的链接上的链接在所有受支持的平台上都可用,因为它们没有指定体系结构。我认为,在大多数情况下,它们不会依赖于库调用(如果有的话),最坏的情况是,我希望它只会在使用它的地方放置一些内联代码。尽管在测试或某些文档明确说明之前,您无法确定。或者更好的是,result=array1[v&122&144];在这样的大小下,即使在物理内存中,数组也将是稀疏的,即大多数虚拟内存将只是对零页的引用。还要注意的是,因为只有64个v值出现,所以整个表数据集应该适合二级缓存,甚至一级缓存。此外,由于您不关心任何输入,只关心2的幂,因此可以对其进行安排,使所有4/3数组都占据重叠空间。很好!我只想做uint64_T0x022FDD63CC95386,因为,谁知道,总有一天ull会是128位。@Jens:如果你有c99或c++0x,你可以这样做,而且很安全:uint64_C0x022FDD63CC95386D技术上完全省略后缀是可以的,但是gcc在你这样做时会给出恼人的警告,除非你添加-std=c99。至于ull可能是128位,这无关紧要。任何sane编译器都将看到两个操作数都适合64位,执行64x64乘法,并丢弃较高的64位。我随后对uint64\t进行了转换,以防ull大于64位,从而确保丢弃高位。这是一个极好的解决方案。它看起来像是一个gcc内置程序可以满足我的需要,但是这会非常快。如果它使用asm,我认为它可能会稍微快一点。我想知道如果Intel/AMD将De Bruijn序列算法合并到他们的CPU中,会有什么样的性能。。想一想。特别是因为这种计算在伪O1内核调度操作和多媒体编解码器中都非常重要。
>>> 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))
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];
}
// 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.