也许你可以帮助我解决以下问题,这可以帮助我加速我正在考虑的内存管理器(我不确定是否存在解决方案 - 我没有找到解决方案)。
我有一个32位寄存器,我需要查找其中是否有n个连续的设置位,如果是,那么它们的偏移量是多少。例如,如果寄存器保持以下值111100000000000000000001111111000且n等于4 - 接受以下任何答案(偏移量从0开始):
3,4,5,6,28
我所拥有的原子操作是所有常规的按位运算(&,|,〜,...),并且还找到最低有效位偏移(上面的寄存器中为3)。算法(假设存在一个) - 应该不超过5个原子操作。
答案 0 :(得分:3)
如果存在执行该算法的算法,则最差情况复杂度至少为O(m-n)
,其中m
是寄存器中的位数,n
是数字你正在寻找的连续设置位。这很容易看到,因为如果设置了所有位,您的算法将必须输出精确的m-n
项,因此它的复杂性不能低一些。
修改强>
这里有类似问题的优雅解决方案Looping through bits in an integer, ruby,找到了1
序列的长度。
如果您事先知道要查找的游戏的n
长度,则此算法仅需n
个步骤。然后可以在约5个步骤中从算法的前一步骤中的尾随零的数量恢复偏移。这不是非常有效,但可能比循环解决方案更好,特别是对于小n
。
编辑2
如果事先知道n
,我们可以为它找出一系列必要的变化。例如。如果我们正在寻找7位运行,那么我们必须做
x &= x >> 1
x &= x >> 3
x &= x >> 1
x &= x >> 1
重点是,如果n/2
为偶数,我们会向右移n
位;如果n
为奇数,我们会向右移{1,然后相应地更新n
(n = n - 1 or n = n / 2
},正如@harold建议的那样。动态估算这些值会很昂贵,但如果我们预先计算它们,那么效率会非常高。
编辑3
更好的是,对于任何n
,只要在ceil(log(2,n))
和floor(n/2)
之间,无论我们采取哪种转变,都需要完全2^floor(log(2,n-1))
步骤。见下面的评论。
答案 1 :(得分:1)
Qnan发布的链接显示了一般案例的优雅解决方案。
对于m的特定值,可以进一步优化。
例如,对于m == 4,您可以这样做:
x &= (x >> 1);
x &= (x >> 2);
// at this point, the first bit set in x indicates a 4 bit set sequence.
对于m == 6:
x &= (x >> 1);
x &= (x >> 1);
x &= (x >> 3);
最后,这只会减少到因子m。
<强>更新强>
另请注意,对于较高的值,在每个可能的位置检查位序列实际上可能更便宜。
例如,对于m = 23,模式只能从0到9的位置开始。
答案 2 :(得分:0)
对于每个可能的字节值(0-255),计算开始时的位数,结束时的位数和字节内连续位的最长数以及该序列的偏移量。例如,对于0x11011101
,开头有2位,末尾有1位,连续有3个连续位。
将此值存储在4个数组中,例如start
,end
,longest
,longest_offset
。
然后,将32位数视为4字节数组,并按如下方式迭代这些字节:
int search_bit_sequence(uint32 num, int desired) {
unsigned char *bytes = (unsigned char *)#
int i, acu;
for (acu = i = 0; i < 4; i++) {
int byte = bytes[i];
acu += start[byte];
if (acu >= desired)
return (i * 8 - (acu - start[byte]));
if (longest[byte] >= desired)
return ( i * 8 + longest_offset[byte]);
if (longest[byte] < 8)
acu = end[byte];
}
return -1; /* not found */
}
更新:请注意,CPU的字节顺序可能需要更改循环方向。
答案 3 :(得分:0)
我检查了this question and answers并提出了以下想法。
int i = n-1;
uint32_t y = x;
while(y && i--) {
y = y & (y << 1);
};
如果有y
个连续设置位,则在上述操作之后n
非零。接下来要做的是找到那里设置的最不重要的值。以下代码将删除除最不重要之外的所有设置位。
z = y - (y & (y-1));
现在我们只有一位设置,我们需要找到该位的位置。我们可以使用带有32个案例的switch语句。
static inline int get_set_position(const uint32_t z) {
switch(z) {
case 0x1:
return 0;
case 0x2:
return 1;
....
.... // upto (1<<31) total 32 times.
}
return -1;
}
最后,为了得到结果,我们需要减少n-1
。所以总程序如下。
static inline int get_set_n_position(const uint32_t x, const uint8_t n) {
if(!n) return -1;
int i = n-1;
uint32_t y = x;
while(y && i--) {
y = y & (y << 1);
};
if(!y) return -1;
uint32_t z = y - (y & (y-1));
if(!z) return -1;
int pos = get_set_position(z);
if(pos < 0) return -1;
assert(pos >= (n-1));
return pos - (n-1);
}
现在有人担心大端。我想我只需要更改big-endian的get_set_position()以使其工作(假设连续的设置位定义根据字节序更改)。
让我分享一个使用gcc提供的builtin_ctzl的测试代码。
OPP_INLINE int get_set_n_position(BITSTRING_TYPE x, const uint8_t n) {
if(!n || n > BIT_PER_STRING) return -1;
int i = n-1;
while(x && i--) {
x = x & (x << 1);
};
if(!x) return -1;
int pos = __builtin_ctzl(x);
return pos - (n-1);
}
代码在O(1)时间内工作,因为32是常量(如@Qnan注意到的)。如果寄存器的大小不同,它也可以在O(n)中工作。
注意:由于评论和单元测试,我修复了错误。