如何在 C 中使用按位或其他有效代码实现逻辑含义?
Posted
技术标签:
【中文标题】如何在 C 中使用按位或其他有效代码实现逻辑含义?【英文标题】:How could I implement logical implication with bitwise or other efficient code in C? 【发布时间】:2010-10-14 16:42:18 【问题描述】:我想实现一个尽可能高效的逻辑操作。我需要这个真值表:
p q p → q
T T T
T F F
F T T
F F T
根据***,这被称为“logical implication”
我一直试图弄清楚如何在不使用条件的情况下使用 C 中的位运算来实现这一点。也许有人对此有一些想法。
谢谢
【问题讨论】:
你需要这个做什么?没有上下文,关于效率的讨论几乎毫无意义。 【参考方案1】:!p || q
速度很快。说真的,别担心。
【讨论】:
谁在乎......按位运算不会比这更快。 是的,实际上我也想知道这是否会像按位一样快。感谢您向我解释这一点。 对于 32 位 int,"~p | q" 的工作量是 "!p || q" 的 32 倍,并且不需要跳转。 litb:由于分支预测未命中,几乎每个 CPU 中的跳转都比算术慢。 太棒了。也许您可以编辑添加此信息的答案。我觉得很有趣。【参考方案2】:~p | q
对于可视化:
perl -e'printf "%x\n", (~0x1100 | 0x1010) & 0x1111'
1011
在紧凑的代码中,这应该比“!p || q”更快,因为后者有一个分支,这可能会由于分支预测错误而导致 CPU 停顿。位版本是确定性的,作为奖励,在 32 位整数中的工作量是布尔版本的 32 倍!
【讨论】:
谢谢!我想问我在哪里可以获得更多关于这些的信息?我的意思是关于这些操作的C实现,所以我可以了解||的细节对比 |等等…… 我已经测试了两个版本。至少在 x86 上的 GCC 坚持在我能想象的每个场景中使用返回 0/1 的分支作为 bool 版本。按位运算没有。【参考方案3】:仅供参考,使用 gcc-4.3.3:
int foo(int a, int b) return !a || b;
int bar(int a, int b) return ~a | b;
给予(来自 objdump -d):
0000000000000000 <foo>:
0: 85 ff test %edi,%edi
2: 0f 94 c2 sete %dl
5: 85 f6 test %esi,%esi
7: 0f 95 c0 setne %al
a: 09 d0 or %edx,%eax
c: 83 e0 01 and $0x1,%eax
f: c3 retq
0000000000000010 <bar>:
10: f7 d7 not %edi
12: 09 fe or %edi,%esi
14: 89 f0 mov %esi,%eax
16: c3 retq
所以,没有分支,但指令数量是原来的两倍。
甚至更好,_Bool
(感谢@litb):
_Bool baz(_Bool a, _Bool b) return !a || b;
0000000000000020 <baz>:
20: 40 84 ff test %dil,%dil
23: b8 01 00 00 00 mov $0x1,%eax
28: 0f 45 c6 cmovne %esi,%eax
2b: c3 retq
因此,使用_Bool
代替int
是个好主意。
自从我今天更新以来,我已经确认 gcc 8.2.0 为_Bool:
产生了类似但不相同的结果
0000000000000020 <baz>:
20: 83 f7 01 xor $0x1,%edi
23: 89 f8 mov %edi,%eax
25: 09 f0 or %esi,%eax
27: c3 retq
【讨论】:
或者您可以只使用gcc -S *.c; cat *.s
并跳过objdump -d *.o
步骤? ;-)
是的,但我记得 objdump 选项,但不记得 gcc 选项:-p
实际上,测试 gcc -S 它提供了 /much/ 更多的输出,所有额外的东西都无关紧要。
我得到了更短的 || 代码带有 _Bool (c99) 或 bool (c++) 的版本
这太疯狂了。关闭优化,按位方式保持那么短,而 ||一路膨胀:p【参考方案4】:
您可以阅读deriving boolean expressions from truth Tables(另见canonical form),了解如何将任何真值表表示为布尔基元或函数的组合。
【讨论】:
非常有趣的链接。谢谢!【参考方案5】:C 布尔值的另一种解决方案(有点脏,但有效):
((unsigned int)(p) <= (unsigned int)(q))
根据 C 标准,0
表示 false,任何其他值表示 true(1
由布尔运算符返回 true,int
类型)。
“脏”是我使用布尔值(p
和q
)作为整数,这与一些强类型策略(例如 MISRA)相矛盾,嗯,这是一个优化问题。你可以一直 #define
它作为一个宏来隐藏脏东西。
对于正确的布尔 p
和 q
(具有 0
或 1
二进制表示)它可以工作。否则,如果 p
和 q
具有任意非零值来表示 true,T->T
可能无法生成 T
。
如果您只需要存储结果,从 Pentium II 开始,就有 cmovcc
(条件移动)指令(如 Derobert 的回答所示)。然而,对于布尔值,即使是 386 也有一个无分支选项,setcc
指令,它在结果字节位置(字节寄存器或内存)中生成 0
或 1
。您还可以在 Derobert 的回答中看到这一点,并且此解决方案还编译为涉及 setcc
的结果(setbe
:如果低于或等于则设置)。
Derobert 和 Chris Dolan 的 ~p | q
变体对于处理大量数据应该是最快的,因为它可以分别处理 p
和 q
的所有位的含义。
请注意,即使 !p || q
解决方案也无法编译为 x86 上的分支代码:它使用 setcc
指令。如果p
或q
可能包含代表真的任意非零值,那么这是最好的解决方案。如果你使用_Bool
类型,它会生成很少的指令。
我在为 x86 编译时得到以下数字:
__attribute__((fastcall)) int imp1(int a, int b)
return ((unsigned int)(a) <= (unsigned int)(b));
__attribute__((fastcall)) int imp2(int a, int b)
return (!a || b);
__attribute__((fastcall)) _Bool imp3(_Bool a, _Bool b)
return (!a || b);
__attribute__((fastcall)) int imp4(int a, int b)
return (~a | b);
组装结果:
00000000 <imp1>:
0: 31 c0 xor %eax,%eax
2: 39 d1 cmp %edx,%ecx
4: 0f 96 c0 setbe %al
7: c3 ret
00000010 <imp2>:
10: 85 d2 test %edx,%edx
12: 0f 95 c0 setne %al
15: 85 c9 test %ecx,%ecx
17: 0f 94 c2 sete %dl
1a: 09 d0 or %edx,%eax
1c: 0f b6 c0 movzbl %al,%eax
1f: c3 ret
00000020 <imp3>:
20: 89 c8 mov %ecx,%eax
22: 83 f0 01 xor $0x1,%eax
25: 09 d0 or %edx,%eax
27: c3 ret
00000030 <imp4>:
30: 89 d0 mov %edx,%eax
32: f7 d1 not %ecx
34: 09 c8 or %ecx,%eax
36: c3 ret
当使用_Bool
类型时,编译器清楚地利用了它只有两个可能的值(0
表示 false 和 1
表示 true),产生与 ~a | b
解决方案非常相似的结果(唯一的区别在于后者对所有位执行补码,而不仅仅是最低位)。
为 64 位编译得到几乎相同的结果。
无论如何,很明显,从避免产生条件的角度来看,方法并不重要。
【讨论】:
这实际上非常漂亮,它反映了这样一个事实,即逻辑蕴涵编码了幂集上的关系的子集,并且(显然)还编码了一个比集上的小于或等于关系。干得好。以上是关于如何在 C 中使用按位或其他有效代码实现逻辑含义?的主要内容,如果未能解决你的问题,请参考以下文章