Bits twiddling hack:每 n 位删除一位的最有效方法?

Posted

技术标签:

【中文标题】Bits twiddling hack:每 n 位删除一位的最有效方法?【英文标题】:Bits twiddling hack: most efficient way to remove one bit every n bits? 【发布时间】:2014-01-15 15:44:49 【问题描述】:

这是我的问题:

我需要在CC++11 中非常高效地执行此操作(我需要在超级计算机上执行数十亿次此操作)。 Nn 在编译时是已知的(模板参数)。最有效的算法是什么?

这是一个例子:

#include <iostream>
#include <climits>
#include <type_traits>
#include <bitset>

template <unsigned int Modulo,
          typename Type,
          unsigned int Size = sizeof(Type)*CHAR_BIT,
          class = typename std::enable_if<std::is_integral<Type>::value
                                       && std::is_unsigned<Type>::value>::type>
inline Type f(Type x)

    // The most inefficient algorithm ever
    std::bitset<Size> bx(x);
    std::bitset<Size> by(0);
    unsigned int j = 0;
    for (unsigned int i = 0; i < Size; ++i) 
        if (i%Modulo) 
            by[j++] = bx[i];
        
    
    return by.to_ullong();


int main()

    std::bitset<64> x = 823934823;
    std::cout<<x<<std::endl;
    std::cout<<(std::bitset<64>(f<2>(x.to_ullong())))<<std::endl;
    return 0;

【问题讨论】:

这个问题是题外话,因为最小理解的东西在哪里?总之,题外话。另外,把我的最低理解要求还给我。 Endrant。 我有一台非超级计算机,我可以在公共汽车上随身携带它,它可以在 四分之一秒 内完成十亿次操作。你确定你真的需要世界上最高效的算法在超级计算机上只执行十亿次吗? 您的问题的答案几乎可以肯定:建立一个查找表。如果N 是 8 而n 在 1 到 7 之间,那么可能只有不到一千种可能性。把它们全部列举出来,把它们放在一个查找表中,你就完成了。 我投了反对票,因为您要求我们为您做事,而您却表现出零努力。你试过什么?为什么很慢?你是如何进行基准测试的?你的结果是什么?是什么让你相信它可以改进? 仅仅因为你包含一张漂亮的图片来解释你的问题并不意味着你已经尝试实际解决它(并且事实上被卡住了)。 【参考方案1】:

语义优先...

从语义上(和概念上,因为你实际上不能在这里使用迭代器),你正在做一个std::copy_if,你的输入和输出范围是一个std::bitset&lt;N&gt;,你的谓词是一个形式的lambda(使用C+ +14 通用 lambda 表示法)

[](auto elem)  return elem % n != 0; 

此算法在分配数和谓词调用数方面具有O(N) 复杂性。因为std::bitset&lt;N&gt; 没有迭代器,所以您必须逐位检查。这意味着带有手写谓词的循环在假设的可迭代std::bitset&lt;N&gt; 上执行与std::copy_if 完全相同的计算。

这意味着就渐近效率而言,您的算法不应被视为低效

...最后优化

因此,鉴于您的算法没有做任何像二次复杂度那样糟糕的事情的结论,它的常数因子可以优化吗? std::bitset 效率的主要来源是您的硬件可以并行处理许多(8、16、32 或 64)位。如果您有权访问该实现,则可以编写自己的 copy_if 来利用这种并行性,例如通过特殊的硬件指令、查找表或某些bit-twiddling algorithm

例如这就是成员函数count() 以及gcc 和SGI 扩展Find_first_()Find_next_() 的实现方式。旧的 SGI 实现使用 256 个条目的查找表来处理位计数和对每个 8 位 char 的位的准迭代。最新的 gcc 版本使用 __builtin_popcountll()__builtin_ctzll() 对每个 64 位字进行人口计数和位查找。

不幸的是,std::bitset 没有公开其底层无符号整数数组。所以如果你想改进你发布的算法,你需要编写你自己的BitSet类模板(可以通过改编你自己的标准库的源代码)并给它一个成员函数copy_if(或类似的),利用你的硬件。与您当前的算法相比,它可以将效率提高 8 到 64 倍。

【讨论】:

以上是关于Bits twiddling hack:每 n 位删除一位的最有效方法?的主要内容,如果未能解决你的问题,请参考以下文章

Bank Hacking CodeForces - 796C

Gosper's Hack (生成 n元集合所有 k 元子集

Leetcode 191 Number of 1 Bits 位运算

191. Number of 1 Bits

Leetcode 190 Reverse Bits 位运算

338. Counting Bits_比特位计数_简单动态规划