我想从64位字符串中删除位(由无符号长整数表示)。我可以通过一系列掩码和移位操作来完成此操作,或者迭代每个位,如下面的代码所示。是否有一些巧妙的比特方法可以让它更快地执行?
public ulong RemoveBits(ulong input, ulong mask)
{
ulong result = 0;
ulong readbit = 1;
ulong writebit =1;
for (int i = 0; i < 64; i++)
{
if ((mask & readbit) == 0) //0 in the mask means retain that bit
{
if ((input & readbit) > 0)
{
result+= writebit;
}
writebit*=2;
}
readbit *= 2;
}
return result;
}
我需要在性能关键的情况下执行RemoveBits
数百万次。
它可能过于抽象而无法提供帮助,但所使用的不同掩码的数量虽然在编译时未知,但是在运行时早期(在性能关键位之前)确定,并且可能少于100基本上,我使用bitstring将n-tuple
和RemoveBits
项目表示到m-tuple
(m < n)
上。
答案 0 :(得分:3)
这称为compress right。不幸的是,如果没有特殊的硬件支持(存在pext,而不是新的),实际上并没有很好的方法。以下是Hackers Delight中的一些方法,修改为C#和64位ulong
,但未经过测试:
ulong compress(ulong x, ulong m) {
ulong r, s, b; // Result, shift, mask bit.
r = 0;
s = 0;
do {
b = m & 1;
r = r | ((x & b) << s);
s = s + b;
x = x >> 1;
m = m >> 1;
} while (m != 0);
return r;
}
这样做的好处是分支比问题中的代码少得多。
还有一种方法可以减少循环迭代次数,但步骤要复杂得多:
ulong compress(ulong x, ulong m) {
ulong mk, mp, mv, t;
int i;
x = x & m; // Clear irrelevant bits.
mk = ~m << 1; // We will count 0's to right.
for (i = 0; i < 6; i++) {
mp = mk ^ (mk << 1); // Parallel prefix.
mp = mp ^ (mp << 2);
mp = mp ^ (mp << 4);
mp = mp ^ (mp << 8);
mp = mp ^ (mp << 16);
mp = mp ^ (mp << 32);
mv = mp & m; // Bits to move.
m = m ^ mv | (mv >> (1 << i)); // Compress m.
t = x & mv;
x = x ^ t | (t >> (1 << i)); // Compress x.
mk = mk & ~mp;
}
return x;
}
答案 1 :(得分:1)
虽然它有the one that inspired this answer,但是这个网站没有这个特殊的操作。
我们的想法是离线计算可以插入以下模板的幻数列表。该模板由重复的基本步骤组成6 = lg 64次:对于k = 1,2,...,6整理输出位mod 2 ** k的索引,假设在每个步骤开始时索引是正确的mod 2 **(k-1)。
例如,假设我们希望转换
x = a.b..c.d
76543210
到
....abcd
76543210.
位a
位于7
位置,需要转到3
(正确的位置mod 2)。位b
位于5
位置,需要转到2
(错误的位置mod 2)。位c
位于2
位置,需要转到1
(错误的位置mod 2)。位d
位于0
位置,需要保留(正确的位置mod 2)。第一个中间步骤是移动b
和c
,如此。
a..b..cd
76543210
这是通过
完成的x = (x & 0b10000001) | ((x >>> 1) & 0b00010010);
//76543210 //76543210
此处>>>
表示逻辑移位,0bxxxxxxxx
表示big-endian二进制文字。现在我们有两个问题:一个在奇数索引位上,一个在偶数上。使这种算法快速的原因是这些算法现在可以并行处理。
为完整起见,其他两项操作如下。位a
现在位于7
位置,需要转到3
(正确的位置mod 4)。位b
现在位于6
位置,需要转到4
(错误的位置模块4)。比特c
和d
需要保留(正确的位置mod 4)。获得
a....bcd
76543210,
我们
x = (x & 0b10000011) | ((x >>> 2) & 0b00000100);
//76543210 //76543210
位a
现在位于7
位置,需要转到3
(错误的位置模块8)。需要保留b
,c
和d
位(正确位置mod 8)。获得
....abcd
76543210,
我们
x = (x & 0b00000111) | ((x >>> 4) & 0b00001000);
//76543210 //76543210
这里有一些概念证明Python(对不起)。
def compute_mask_pairs(retained_indexes):
mask_pairs = []
retained_indexes = sorted(retained_indexes)
shift = 1
while (retained_indexes != list(range(len(retained_indexes)))):
mask0 = 0
mask1 = 0
for (i, j) in enumerate(retained_indexes):
assert (i <= j)
assert ((i % shift) == (j % shift))
if ((i % (shift * 2)) != (j % (shift * 2))):
retained_indexes[i] = (j - shift)
mask1 |= (1 << j)
else:
mask0 |= (1 << j)
mask_pairs.append((mask0, mask1))
shift *= 2
return mask_pairs
def remove_bits_fast(mask_pairs, x):
for (log_shift, (mask0, mask1)) in enumerate(mask_pairs):
x = ((x & mask0) | ((x >> (2 ** log_shift)) & mask1))
return x
def remove_bits_slow(retained_indexes, x):
return sum(((((x // (2 ** j)) % 2) * (2 ** i)) for (i, j) in enumerate(sorted(retained_indexes))))
def test():
k = 8
for mask in range((2 ** k)):
retained_indexes = {i for i in range(k) if (((mask // (2 ** k)) % 2) == 0)}
mask_pairs = compute_mask_pairs(retained_indexes)
for x in range((2 ** k)):
assert (remove_bits_fast(mask_pairs, x) == remove_bits_slow(retained_indexes, x))
test()
答案 2 :(得分:0)
有一个非常好的网站已经存在了很长时间,称为Bit Twiddling Hacks。它有许多用于位操作的快速算法。你可能想要看一下(我在这里逐字复制,这不是我自己的工作)是这个算法:
有条件地设置或清除没有分支的位
bool f; // conditional flag unsigned int m; // the bit mask unsigned int w; // the word to modify: if (f) w |= m; else w &= ~m; w ^= (-f ^ w) & m; // OR, for superscalar CPUs: w = (w & ~m) | (-f & m);
在某些架构上,缺乏分支可以弥补 什么似乎是两倍的操作。例如,非正式的 AMD Athlon™XP 2100+的速度测试表明速度提高了5-10%。 英特尔酷睿2双核处理器的超标量版本比其快16% 首先。 Glenn Slayden告诉我第一个表达方式 2003年12月11日.Mandy Yu与我分享了超标量版本 2007年4月3日,并在2天后提醒我输入错字。