我编写了一种算法(取自“ C编程语言”),可以非常快速地计算1位的数量:
int countBit1Fast(int n)
{
int c = 0;
for (; n; ++c)
n &= n - 1;
return c;
}
但是一个朋友告诉我__builtin__popcount(int)
快很多,但便携性较差。我尝试一下,速度快了很多倍!为什么这么快?我想尽可能快地计算位数,但又不依赖于特定的编译器。
编辑:我可能会在PIC微控制器以及非英特尔处理器上使用它,因此我需要最大的可移植性。
答案 0 :(得分:1)
正如其他人所提到的,__buildin__popcount()
很快,因为它使用单个x86指令。
如果您想要比不使用任何处理器或编译器的东西快得多的东西,则可以创建包含256个条目的查找表:
int bitcount[] = {
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
};
然后使用它来获取每个字节的位数:
int countBit1Fast(int n)
{
int i, count = 0;
unsigned char *ptr = (unsigned char *)n;
for (i=0;i<sizeof(int);i++) {
count += bitcount[ptr[i]];
}
return count;
}
答案 1 :(得分:1)
我编写了一种算法(取自“ C编程语言”),可以非常快速地计算1位的数量:
我不明白为什么有人会把您的方法描述为“非常快”。这有点聪明,并且平均而言应该比幼稚的替代方法更快。它也不取决于int
表示形式的宽度,这是一个加号。我观察到它对于否定参数具有不确定的行为,但这是按位运算符和函数的共同主题。
让我们分析一下,假设一个非负参数:
int c = 0;
for (; n; ++c)
n &= n - 1;
执行了多少次循环迭代?
值的二进制表示形式中的每1位1,而不管值中每个位所在的何处
每个迭代执行多少工作
c
n
与零进行比较(在退出循环时再加上其中的一个)n
递减1 这将忽略读取和存储,通过将操作数保存在寄存器中,很可能可以使它们变得免费或特别便宜。如果我们假设每种方法的成本均等,则每个迭代要进行四个操作。对于随机的32位整数,平均将进行16次迭代,平均总共进行 65次操作。 (最好的情况是一个操作,但是最坏的情况是129,这并不比单纯的实现好。)
__builtin__popcount()
使用一条指令,而不管支持该指令的平台(例如您的指令)上的输入如何。但是,即使在那些没有针对性的指令的情况下,它也可以更快地完成(平均而言)。
@dbush提出了一种这样的机制,它具有与您提出的机制类似的优点。特别是,它不依赖于预先选择的整数宽度,尽管它确实依赖于1位所驻留的表示形式中的 where ,但是对于某些参数(较小的),它的运行速度确实比其他参数快。如果我计算正确的话,那么在32位随机输入中,平均大约20次操作:在四个循环迭代中,每个迭代要进行5次(只有0.4%的随机输入需要少于四个迭代)。我在计算每次迭代读取的一张表,我认为可以从缓存中获取该表,但它可能仍然不如对寄存器中已保存的值进行算术运算的速度快。
严格计算的是:
int countBit1Fast(uint32_t n) {
n = (n & 0x55555555u) + ((n >> 1) & 0x55555555u);
n = (n & 0x33333333u) + ((n >> 2) & 0x33333333u);
n = (n & 0x0f0f0f0fu) + ((n >> 4) & 0x0f0f0f0fu);
n = (n & 0x00ff00ffu) + ((n >> 8) & 0x00ff00ffu);
n = (n & 0x0000ffffu) + ((n >>16) & 0x0000ffffu);
return n;
}
这很容易计算:五个加法,五个移位和十个按位“与”运算,以及5个常数负载,每个输入总共进行 25个运算(并且仅上升)对于64位输入,则为30,尽管现在是64位操作,而不是32位操作)。但是,此版本本质上取决于输入数据类型的特定大小。
答案 2 :(得分:0)
__buildin__popcount()
之所以如此之快,是因为它的gcc扩展利用了内置的硬件指令。如果您愿意用体系结构的可移植性来换取编译器的可移植性,请研究快速的intel内在函数,特别是:
_mm_popcnt_u64()
或
_mm_popcnt_u32()
然后必须包括<mmintrin.h>
头文件才能使用这些内在函数,但是它们将与非gcc编译器一起使用。您可能还必须提供目标体系结构,才能使用-march=native
之类的东西来内联功能(这是严格要求的)。