如何在 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) &lt;= (unsigned int)(q))

根据 C 标准,0 表示 false,任何其他值表示 true(1 由布尔运算符返回 true,int 类型)。

“脏”是我使用布尔值(pq)作为整数,这与一些强类型策略(例如 MISRA)相矛盾,嗯,这是一个优化问题。你可以一直 #define 它作为一个宏来隐藏脏东西。

对于正确的布尔 pq(具有 01 二进制表示)它可以工作。否则,如果 pq 具有任意非零值来表示 true,T-&gt;T 可能无法生成 T

如果您只需要存储结果,从 Pentium II 开始,就有 cmovcc(条件移动)指令(如 Derobert 的回答所示)。然而,对于布尔值,即使是 386 也有一个无分支选项,setcc 指令,它在结果字节位置(字节寄存器或内存)中生成 01。您还可以在 Derobert 的回答中看到这一点,并且此解决方案还编译为涉及 setcc 的结果(setbe:如果低于或等于则设置)。

Derobert 和 Chris Dolan 的 ~p | q 变体对于处理大量数据应该是最快的,因为它可以分别处理 pq 的所有位的含义。

请注意,即使 !p || q 解决方案也无法编译为 x86 上的分支代码:它使用 setcc 指令。如果pq 可能包含代表真的任意非零值,那么这是最好的解决方案。如果你使用_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 中使用按位或其他有效代码实现逻辑含义?的主要内容,如果未能解决你的问题,请参考以下文章

C语言中位移位运算符?

java逻辑运算符作用

Java中逻辑与,逻辑或,按位与,按位或的区分

c语言 操作符系统解读

常量的按位或

c语言的按位运算符怎么操作!?