c - 如何使用按位运算根据另外两个字节分配一个字节的特定位?(根据掩码进行位混合)
问题描述
我有 3 个字节。
- 一个字节确定第 3 个字节的哪些位需要更改(1 表示位需要更改,0 表示不应发生更改)。
- 第 2 个字节决定变化的位是 1 还是 0。
- 第三个字节是发生变化的地方。
有没有办法使用按位运算符来实现这一点?如果是这样,怎么做?一个简单的公式或程序来实现这一点会很好(最好在 c 中)。
例子:
BitsToAssign: 0b01101011
ValuesToAssign: 0b01000010
ByteToAlter: 0b11110001
ExpectedResult: 0b11010010
解决方案
对此的标准 bithack 是“根据掩码合并来自两个值的位”-我将输入的变量名称添加到 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)
- 清除您没有保留在每个输入中的位,然后将它们组合在一起。
工作原理a ^ ((a ^ b) & mask)
:
bitdiff = a^b
是这些输入之间的位差。
它有1
它们不同的地方,也有0
它们相同的地方。(根据 XOR 的定义)。
a ^ bitdiff
会翻转a
与b
. 事实上,b == a ^ bitdiff
.
证明这是真的的一种方法是 XOR 是关联的,所以 a ^ (a^b)
= (a ^ a) ^ b
。
和x^x
= 0,就像x-x = 0
.
0 ^ x = x
,所以(a^a) ^ b
= 0 ^ b
= b
。
但是,如果我们将 bitdiff 屏蔽为仅从某些位置设置位a
到位b
,我们就实现了我们的目标:根据掩码进行按位混合。 blend = a ^ (bitdiff & mask);
(a & ~mask) | (b & mask)
简单版的特殊情况
如果您的输入被安排成ValuesToAssign
只有1
在掩码选择的位置有任何位,您可以优化掉b & mask
部分,只留下(a & ~mask) | b
. (Eraklon 的回答)。清除未选择的位,然后在新值中进行 OR 以设置应设置的任何位。
另一个特殊情况是 when ValuesToAssign == BitsToAssign
,即修改只设置位,从不清除它们。这就是 OR 所做的,因此这种情况当然会优化为a | b
,而无需a
在 ORing 之前清除位。
效率:
r = a ^ ((a ^ b) & mask);
只有 3 个布尔运算,而如果所有三个输入都是运行时变量,
则为 4个。(a & ~mask) | (b & mask)
(一个按位非,两个与,加上一个或)。
如果mask
是一个常量,那么常量传播到~mask
使其成为一个常量,并且大多数机器可以使用至少一个 8 位与掩码进行与立即数。所以你仍然只需要 3 条指令:两个 AND(带反常数)和一个 OR。
一些机器(比如带有 BMI1 的 x86)甚至有一条andn
指令x & ~y
,允许(a & ~mask) | (b & mask)
使用 3 条指令来完成。
对于延迟,(a & ~mask) | (b & mask)
具有更多的指令级并行性。如果mask
和在和~mask
之前准备好,则只有两个并行的 AND 操作和一个 OR,从和输入准备好到输出准备好。在普通的超标量机器上(可以在同一个周期中执行两个独立的 AND 操作),从输入到输出只有 2 个周期的延迟。(再次,假设已提前准备好,或者存在类似的指令可以在单个操作中执行)。a
b
a
b
mask
andn
a & ~mask
如果关键路径通过(即它没有提前准备好),并且mask
您没有像andn
做一个操作的指令,那么从to的延迟是 3 个操作,,和。(假设可以并行运行而不会减慢这三个中的任何一个)。~
&
mask
result
~
&
|
b & mask
(a & ~mask) | (b & mask)
现代 OoO 执行机器上的延迟。
(与吞吐量成本不同)
- 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 - 这在 SIMD 编程中称为混合。IDK 如果其他人使用我喜欢用于此操作的“位混合”术语,但 IMO 清楚地描述了它。x86 的 AVX-512 扩展具有 3 输入布尔运算
vpternlog
,带有来自 8 位立即数的真值表,因此可以在单个指令中完成。在两个字节之间的给定点交换位- 相同的 bithack 想法,但将掩码位差应用于两个输入以在掩码位置交换位。
https://catonmat.net/low-level-bit-hacks - 从对运算符的介绍开始(如
^
按位异或)。包括使用 + 和 - 的 bithacks(以及命中 1 或 0 的进位传播效果,例如x & (x-1)
清除最右边的设置位。)https://agner.org/optimize/了解更多关于现代 CPU 调优的信息。
推荐阅读
- angularjs - Angularjs 使用 ng-class 和 ui-grid 组件
- java - 引起:java.io.IOException:长度 1279873876 超出限制:26
- excel - 将一组在执行时定义的单元格分配给一个范围
- apache-spark - 丢失的执行者 Spark
- c# - C#打印图像时如何在e.Drawing中下一行
- java - 使用客户端身份验证访问 Web 服务:handshake_failure
- android - 如何在由 apktool 反编译的应用程序中为可绘制对象添加新的资源标识符?
- c# - 将实体插入到 MongoDb 异步不起作用
- delphi - Fastreport 和 Delphi Berlin:如何将多个记录字段放在一行中
- c# - System.Mail.Net 通过 Gmail 但重定向到身份门户