【问题标题】:Fast way to remove bits from a ulong从 ulong 中删除位的快速方法
【发布时间】:2014-08-25 17:34:07
【问题描述】:

我想从 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。本质上,我'm 使用位串来表示n-tuple,并将RemoveBits 投影到m-tuple (m &lt; n)

【问题讨论】:

  • 什么语言?如果这太抽象而无法仅限于一种语言,那么在“计算机科学”SE 网站上可能会更好。
  • 所以你想删除input 中所有在mask 中设置的位?那就试试input &amp; ~mask
  • @Gumbo - 我不只是想归零一点,我还想将任何位右移到它的左边。
  • @Conduit。我正在使用 C#。

标签: algorithm bit-manipulation


【解决方案1】:

这被称为compress right。不幸的是,如果没有特殊的硬件支持(以pext 的形式存在,相当新),就没有真正的好方法。以下是 Hackers Delight 中给出的一些方法,修改为 C# 和 64 位 ulongs,但未经测试:

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

【讨论】:

  • 我发布的或多或少是第二个版本,带有x-independent 计算。
  • @DavidEisenstat 我相信你的话,它看起来很复杂
【解决方案2】:

bit twiddling hacks 网站没有这个特殊的操作,虽然它有the one that inspired this answer

这个想法是离线计算一个可以插入以下模板的幻数列表。该模板由一个基本步骤组成,重复 6 = lg 64 次:纠正输出位的索引 mod 2**k for k = 1, 2, ..., 6,假设在每个步骤开始时索引为正确的 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

这里&gt;&gt;&gt; 表示逻辑移位,0bxxxxxxxx 表示大端二进制字面量。现在我们有两个问题:一个是奇数位,一个是偶数位。使这个算法快速的原因是这些现在可以并行处理。

为了完整起见,其他两个操作如下。位 a 现在位于位置 7 并且需要转到 3(正确位置 mod 4)。位 b 现在位于位置 6 并且需要转到 4(不正确的位置 mod 4)。位 cd 需要保留(正确位置 mod 4)。得到

a....bcd
76543210,

我们这样做

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

a 现在位于位置 7 并且需要转到 3(不正确的位置 mod 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()

【讨论】:

    【解决方案3】:

    有一个很好的网站,已经存在了很长时间,叫做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%。 Intel Core 2 Duo 运行超标量版本的速度比英特尔酷睿 2 Duo 快约 16% 首先。 Glenn Slayden 告诉我关于 2003 年 12 月 11 日。Marco Yu 与我分享了超标量版本 2007 年 4 月 3 日,并在 2 天后提醒我有一个错字。

    【讨论】:

      猜你喜欢
      • 2023-03-25
      • 1970-01-01
      • 2019-01-27
      • 1970-01-01
      • 2019-06-27
      • 1970-01-01
      • 1970-01-01
      • 2020-09-19
      • 2011-08-10
      相关资源
      最近更新 更多