标准的黑客行为是“根据掩码合并两个值的位 https://graphics.stanford.edu/%7Eseander/bithacks.html#MaskedMerge“ - 我将您的变量名称添加到 Sean Anderson 的 Bithacks 集合中的现有评论中。
unsigned int a; // (ByteToAlter) value to merge in non-masked bits
unsigned int b; // (ValuesToAssign) value to merge in masked bits
unsigned int mask; // (BitsToAssign) 1 where bits from b should be selected; 0 where from a.
unsigned int r; // result of (a & ~mask) | (b & mask) goes here
r = a ^ ((a ^ b) & mask);
正如 bithack 评论所指出的,直接的方法是(a & ~mask) | (b & mask)
- 清除每个输入中未保留的位,然后将它们组合在一起。
How a ^ ((a ^ b) & mask)
works:
bitdiff = a^b
是这些输入之间的按位差异。
它有一个1
它们的不同之处,以及0
它们是相同的。 (根据异或的定义)。
a ^ bitdiff
会翻转每一位a
这不同于b
。实际上,b == a ^ bitdiff
.
证明这一点的一种方法是 XOR 是结合的,所以a ^ (a^b)
= (a ^ a) ^ b
.
And x^x
= 0,就像x-x = 0
.
0 ^ x = x
, so (a^a) ^ b
= 0 ^ b
= b
.
但是如果我们将位差异屏蔽为仅设置位a
到位从b
在某些位置,我们已经实现了目标:根据掩码按位混合。blend = a ^ (bitdiff & mask);
的特殊情况(a & ~mask) | (b & mask)
简单版
如果你的输入是这样安排的ValuesToAssign
仅有任何一个1
位在掩码选择的位置,您可以优化掉b & mask
部分,只留下(a & ~mask) | b
. (埃拉克隆的回答 https://stackoverflow.com/questions/68048922/how-to-use-bitwise-operations-to-assign-specific-bits-of-a-byte-according-to-two/68049185#68049185)。清除未选择的位,然后在新值中进行“或”操作以设置应设置的任何位。
另一个特殊情况是当ValuesToAssign == BitsToAssign
,即修改只会设置位,而不会清除它们。这就是 OR 所做的,所以当然这个案例优化为a | b
,无需清除位a
在 O 环之前。
效率:
r = a ^ ((a ^ b) & mask);
只有3个布尔运算,
与 4 相比(a & ~mask) | (b & mask)
如果所有三个输入都是运行时变量。 (一个按位“非”,两个“与”,加上一个“或”)。
If mask
是一个常数,然后常数传播到~mask
使其成为常数,并且大多数机器可以使用至少 8 位 AND 掩码进行 AND 立即运算。所以你仍然只需要 3 条指令:两个 AND(带有逆常量)和一个 OR。
有些机器(例如带有 BMI1 的 x86)甚至有一个andn https://www.felixcloutier.com/x86/andn的指令x & ~y
,允许(a & ~mask) | (b & mask)
通过 3 条指令即可完成。
对于延迟,(a & ~mask) | (b & mask)
有更多指令级并行性. If mask
and ~mask
提前准备好a
and b
,只有两个并行的 AND 运算和一个 OR,来自a
and b
输入准备好到输出准备好。在普通的超标量机器(可以在同一周期内执行两个独立的 AND 运算)上,从输入到输出只有 2 个周期的延迟。 (再次假设mask
提前准备好,或者像这样的指令andn
存在是为了做a & ~mask
在一次操作中)。
如果关键路径经过mask
(即它还没有准备好),并且你没有像这样的指令andn
to do ~
and &
作为一项操作,延迟来自mask
to result
是 3 次操作,~
, &
, and |
。 (假设b & mask
可以并行运行,而不会减慢这三个中任何一个的速度)。
延迟时间为(a & ~mask) | (b & mask)
在现代 OoO 执行机器上。
(与吞吐量成本不同 https://stackoverflow.com/questions/692718/how-many-cpu-cycles-are-needed-for-each-assembly-instruction/44980899#44980899)
- a -> 结果:2 个周期
- b -> 结果:2 个周期
- 掩码 -> 结果:3 个周期(或在某些机器上 2 个周期)
但位差方式没有任何ILP;这是一个由 3 个操作组成的链。a^b
要求这两个输入都为第一步做好准备,然后mask
需要准备好& mask
步。决赛a ^ ...
步骤是使用之前已经需要的输入。所以只有 3 个操作,但它们是连续的。
延迟时间为a ^ ((a ^ b) & mask)
在现代 OoO 执行机器上。
- a -> 结果:3 个周期
- b -> 结果:3 个周期
- 掩模 -> 结果:2 个周期
相关问答:
-
根据掩码合并位序列a和b https://stackoverflow.com/q/39282691- 这在 SIMD 编程中称为混合。如果其他人使用我喜欢用于此操作的“位混合”术语,我不知道,但 IMO 清楚地描述了它。 x86 的 AVX-512 扩展具有 3 输入布尔运算vpternlog
具有来自 8 位立即数的真值表,因此可以在一条指令中完成。
-
在两个字节之间的给定点交换位 https://stackoverflow.com/questions/3562347/swapping-bits-at-a-given-point-between-two-bytes/50839334#50839334- 相同的 bithack 想法,但将屏蔽位差异应用于both输入以交换掩码位置处的位。
-
https://catonmat.net/low-level-bit-hacks https://catonmat.net/low-level-bit-hacks- 从对操作员的介绍开始(例如^
按位异或)。包括使用 + 和 - 的位黑客(以及击中 1 或 0 的进位传播效果,例如x & (x-1)
清除最右边的设置位。)
-
https://agner.org/optimize/ https://agner.org/optimize/了解有关现代 CPU 调优的更多信息。