从ulong中删除位的快速方法

时间:2014-08-25 17:34:08

标签: algorithm bit-manipulation

我想从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-tupleRemoveBits项目表示到m-tuple (m < n)上。

3 个答案:

答案 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)。第一个中间步骤是移动bc,如此。

a..b..cd
76543210

这是通过

完成的
x = (x & 0b10000001) | ((x >>> 1) & 0b00010010);
         //76543210                 //76543210

此处>>>表示逻辑移位,0bxxxxxxxx表示big-endian二进制文字。现在我们有两个问题:一个在奇数索引位上,一个在偶数上。使这种算法快速的原因是这些算法现在可以并行处理。

为完整起见,其他两项操作如下。位a现在位于7位置,需要转到3(正确的位置mod 4)。位b现在位于6位置,需要转到4(错误的位置模块4)。比特cd需要保留(正确的位置mod 4)。获得

a....bcd
76543210,

我们

x = (x & 0b10000011) | ((x >>> 2) & 0b00000100);
         //76543210                 //76543210

a现在位于7位置,需要转到3(错误的位置模块8)。需要保留bcd位(正确位置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天后提醒我输入错字。