将特定位置的位收集为一个新值

Gather bits at specific positions into a new value

本文关键字:新值 一个 定位 位置      更新时间:2023-10-16

我有一个大小为N个字符的位掩码,这是静态已知的(即可以在编译时计算,但它不是一个常量,所以我不能把它写下来),位设置为1表示"想要的"位。我有一个同样大小的值,只有在运行时才知道。我想从这个值中收集"想要的"位,按顺序,放到一个新值的开头。为简单起见,我们假设所需的位数为<= 32。

完全未优化的参考代码,希望有正确的行为:

template<int N, const char mask[N]>
unsigned gather_bits(const char* val)
{
    unsigned result   = 0;
    char*    result_p = (char*)&result;
    int      pos      = 0;
    for (int i = 0; i < N * CHAR_BIT; i++)
    {
        if (mask[i/CHAR_BIT] & (1 << (i % CHAR_BIT)))
        {
            if (val[i/CHAR_BIT] & (1 << (i % CHAR_BIT)))
            {
                if (pos < sizeof(unsigned) * CHAR_BIT)
                {
                    result_p[pos/CHAR_BIT] |= 1 << (pos % CHAR_BIT);
                } 
                else
                {
                    abort();
                }
            }
            pos += 1;
        }
    }
    return result;
}

虽然我不确定这个公式是否真的允许在编译时访问掩码的内容。但是在任何情况下,它都是可用的,也许一个constexpr函数会是一个更好的主意。我不是在这里寻找必要的c++魔法(我会弄清楚的),只是算法。

一个输入/输出示例,为了清晰起见,使用16位值和虚构的二进制表示法:

mask   = 0b0011011100100110
val    = 0b0101000101110011
--
wanted = 0b__01_001__1__01_ // retain only those bits which are set in the mask
result = 0b0000000001001101 // bring them to the front
                   ^ gathered bits begin here

我的问题是:

  • 这样做的最有效的方法是什么?(有什么硬件说明可以帮助吗?)

  • 如果掩码和值都被限制为unsigned,所以一个单词,而不是一个无界的字符数组呢?那么,是否可以用一个固定的、简短的指令序列来完成?

pext(并行位提取),这正是你在英特尔Haswell中想要的。我不知道该指令的性能如何,但可能比其他指令更好。此操作也称为"右压缩"或简称为"压缩",Hacker’s Delight的实现如下:

unsigned compress(unsigned x, unsigned m) {
   unsigned 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 < 5; i++) {
      mp = mk ^ (mk << 1);             // Parallel prefix. 
      mp = mp ^ (mp << 2); 
      mp = mp ^ (mp << 4); 
      mp = mp ^ (mp << 8); 
      mp = mp ^ (mp << 16); 
      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; 
}