基于位掩码从值中异或所有位的最快方法?
Fastest Way to XOR all bits from value based on bitmask?
我遇到了一个有趣的问题,我正在寻找一种更有效的做事方式。
假设我们有一个值(二进制)
(VALUE) 10110001
(MASK) 00110010
----------------
(AND) 00110000
现在,我需要能够对在 (MASK)
值中设置的 (AND)
值的任何位进行异或(总是从最低位到最高位):
(RESULT) AND<sub>1</sub>(0) xor AND<sub>4</sub>(1) xor AND<sub>5</sub>(1) = 0
现在,从理论上讲,这肯定很快,因为我可以看到掩码中设置了哪些位。在我看来,以编程方式我需要继续右移 MASK 直到找到一个设置位,将它与一个单独的值进行异或,然后循环直到整个字节完成。
谁能想到更快的方法?我正在寻找用最少的操作和存储的值来做到这一点的方法。
利用与 0 的 XOR 不会改变任何东西,可以应用掩码然后无条件地将所有位异或在一起,这可以通过并行前缀方式完成。所以像这样(未测试):
x = m & v;
x ^= x >> 16;
x ^= x >> 8;
x ^= x >> 4;
x ^= x >> 2;
x ^= x >> 1;
result = x & 1
您可以根据需要使用更多(或更少)步骤,这是针对 32 位的。
如果我没理解错的话,你有
result = value & mask
并且您想将 mask & result
的 1 位异或在一起。一系列位的异或与计算位数并检查该计数是偶数还是奇数相同。如果是奇数,则异或为 1;如果偶数,XOR 将给出 0.
count_bits(mask & result) % 2 != 0
mask & result
可以简化为result
。您不需要再次使用 mask
与它。 % 2 != 0
也可以写成 & 1
.
count_bits(result) & 1
至于如何计算位,Bit Twiddling Hacks web page gives a number of bit counting algorithms。
Counting bits set, Brian Kernighan's way
unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
v &= v - 1; // clear the least significant bit set
}
Brian Kernighan's method goes through as many iterations as there are
set bits. So if we have a 32-bit word with only the high bit set, then
it will only go once through the loop.
如果您要使用该实现,您可以进一步优化它。如果您考虑一下,您不需要完整的位数。您只需要跟踪它们的奇偶校验。您可以在每次迭代中翻转 c
而不是计算位数。
unsigned bit_parity(unsigned v) {
unsigned c;
for (c = 0; v; c ^= 1) {
v &= v - 1;
}
}
(感谢Slava的建议。)
如果我对这个问题的理解正确,你想要的是从 MASK 中设置的 VALUE 中获取每一位,并计算这些位的异或。
首先,请注意,将值与 0 进行异或运算不会改变结果。因此,要忽略一些位,我们可以将它们视为零。
因此,对 VALUE 中设置的位与 MASK 中的位进行异或运算等同于对 VALUE 和 MASK 中的位进行异或运算。
现在请注意,如果设置的位数为偶数,则结果为0,如果为奇数,则为1。
这意味着我们要计算设置位的数量。一些 architectures/compilers 有办法快速计算这个值。例如,在 GCC 上,这可以通过 __builtin_popcount
.
获得
所以在 GCC 上,这可以用以下方法计算:
int set_bits = __builtin_popcount(value & mask);
return set_bits % 2;
如果你希望代码是可移植的,那么这是行不通的。但是,this answer 中的注释建议某些编译器可以内联 std::bitset::count
以有效地获得相同的结果。
如果在代码主体中使用 v &= v - 1
,需要注意的一个重要问题是它会在进行计数时将 v
的值更改为 0
。使用其他方法,该值将更改为 1
的数量。虽然计数逻辑通常包装为一个函数,这不再是一个问题,但如果您需要在代码主体中呈现计数逻辑,则必须保留 v
的副本,如果该值是又需要了。
除了介绍的其他两种方法外,以下是另一种最喜欢的位旋转技巧,它通常比循环方法对更大的数字有更好的性能:
/* get the population 1's in the binary representation of a number */
unsigned getn1s (unsigned int v)
{
v = v - ((v >> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
v = (v + (v >> 4)) & 0x0F0F0F0F;
v = v + (v << 8);
v = v + (v << 16);
return v >> 24;
}
我遇到了一个有趣的问题,我正在寻找一种更有效的做事方式。
假设我们有一个值(二进制)
(VALUE) 10110001
(MASK) 00110010
----------------
(AND) 00110000
现在,我需要能够对在 (MASK)
值中设置的 (AND)
值的任何位进行异或(总是从最低位到最高位):
(RESULT) AND<sub>1</sub>(0) xor AND<sub>4</sub>(1) xor AND<sub>5</sub>(1) = 0
现在,从理论上讲,这肯定很快,因为我可以看到掩码中设置了哪些位。在我看来,以编程方式我需要继续右移 MASK 直到找到一个设置位,将它与一个单独的值进行异或,然后循环直到整个字节完成。
谁能想到更快的方法?我正在寻找用最少的操作和存储的值来做到这一点的方法。
利用与 0 的 XOR 不会改变任何东西,可以应用掩码然后无条件地将所有位异或在一起,这可以通过并行前缀方式完成。所以像这样(未测试):
x = m & v;
x ^= x >> 16;
x ^= x >> 8;
x ^= x >> 4;
x ^= x >> 2;
x ^= x >> 1;
result = x & 1
您可以根据需要使用更多(或更少)步骤,这是针对 32 位的。
如果我没理解错的话,你有
result = value & mask
并且您想将 mask & result
的 1 位异或在一起。一系列位的异或与计算位数并检查该计数是偶数还是奇数相同。如果是奇数,则异或为 1;如果偶数,XOR 将给出 0.
count_bits(mask & result) % 2 != 0
mask & result
可以简化为result
。您不需要再次使用 mask
与它。 % 2 != 0
也可以写成 & 1
.
count_bits(result) & 1
至于如何计算位,Bit Twiddling Hacks web page gives a number of bit counting algorithms。
Counting bits set, Brian Kernighan's way
unsigned int v; // count the number of bits set in v unsigned int c; // c accumulates the total bits set in v for (c = 0; v; c++) { v &= v - 1; // clear the least significant bit set }
Brian Kernighan's method goes through as many iterations as there are set bits. So if we have a 32-bit word with only the high bit set, then it will only go once through the loop.
如果您要使用该实现,您可以进一步优化它。如果您考虑一下,您不需要完整的位数。您只需要跟踪它们的奇偶校验。您可以在每次迭代中翻转 c
而不是计算位数。
unsigned bit_parity(unsigned v) {
unsigned c;
for (c = 0; v; c ^= 1) {
v &= v - 1;
}
}
(感谢Slava的建议。)
如果我对这个问题的理解正确,你想要的是从 MASK 中设置的 VALUE 中获取每一位,并计算这些位的异或。
首先,请注意,将值与 0 进行异或运算不会改变结果。因此,要忽略一些位,我们可以将它们视为零。
因此,对 VALUE 中设置的位与 MASK 中的位进行异或运算等同于对 VALUE 和 MASK 中的位进行异或运算。
现在请注意,如果设置的位数为偶数,则结果为0,如果为奇数,则为1。
这意味着我们要计算设置位的数量。一些 architectures/compilers 有办法快速计算这个值。例如,在 GCC 上,这可以通过 __builtin_popcount
.
所以在 GCC 上,这可以用以下方法计算:
int set_bits = __builtin_popcount(value & mask);
return set_bits % 2;
如果你希望代码是可移植的,那么这是行不通的。但是,this answer 中的注释建议某些编译器可以内联 std::bitset::count
以有效地获得相同的结果。
如果在代码主体中使用 v &= v - 1
,需要注意的一个重要问题是它会在进行计数时将 v
的值更改为 0
。使用其他方法,该值将更改为 1
的数量。虽然计数逻辑通常包装为一个函数,这不再是一个问题,但如果您需要在代码主体中呈现计数逻辑,则必须保留 v
的副本,如果该值是又需要了。
除了介绍的其他两种方法外,以下是另一种最喜欢的位旋转技巧,它通常比循环方法对更大的数字有更好的性能:
/* get the population 1's in the binary representation of a number */
unsigned getn1s (unsigned int v)
{
v = v - ((v >> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
v = (v + (v >> 4)) & 0x0F0F0F0F;
v = v + (v << 8);
v = v + (v << 16);
return v >> 24;
}