布尔运算的比较算子

时间:2016-10-16 10:22:46

标签: c++ boolean comparison-operators boolean-operations

在C ++中,逻辑运算符&&||!在那里,对应于连接,析取,否定{{0分别。

但我注意到比较运算符==!=<><=>=可以用作布尔值好!鉴于PQ是布尔值:

P == Q是bicontitional

P != Q是独家分离

P < Q是相反的非复制

P > Q是非复制

P <= Q含义为

P >= Q是相反的含义

我的问题是:

  1. 这个技巧会让程序表现得更好吗?

  2. 是否有使用此技巧的示例代码(使用任何语言)?

2 个答案:

答案 0 :(得分:4)

  

这个技巧会增加性能吗?

在任何有益处的处理器上,当PQ是简单变量时,这很简单,你应该期望编译器在不需要任何源代码的情况下使用它重写。

但请注意,P < Q通常与!P && Q有明显的缺点:它需要评估Q,当结果已知时P评估为true。这同样适用于所有其他关系运算符。

  

是否有使用此技巧的示例代码(使用任何语言)?

不是一个技巧,但因为它可以说导致代码更容易理解(不是任何特定的语言):

if ((a == null) != (b == null))
  throw "a and b must either both be null, or both be non-null";

可以用^编写。哪个更容易阅读是一个意见问题。

答案 1 :(得分:2)

实际上我认为它可能会使代码更快。这是第一个函数的代码:

bool biconditional(bool a, bool b)
{
    return (a && b) || (!a && !b);
}

bool biconditional_trick(bool a, bool b)
{
    return a == b;
}

生成的程序集:

biconditional(bool, bool):
        mov     eax, esi
        xor     eax, 1
        xor     eax, edi
        ret
biconditional_trick(bool, bool):
        cmp     dil, sil
        sete    al
        ret

我使用Compiler Explorer的gcc 5.3和标记-O3 -Wall -fno-verbose-asm -march=haswell

显然你可以削减1条指令。有趣的是,gcc没有进行这种优化。您可能希望向他们发送电子邮件并询问原因:https://gcc.gnu.org/lists.html

编辑:另一个答案很明确:通过修剪不必要的部分可以更快地评估逻辑表达式。为了演示,我重写了代码以使用对返回bool而不是bool参数的函数的调用:

bool fa();
bool fb();

bool biconditional_with_function()
{
    return (fa() && fb()) || (!fa() && !fb());
}

bool biconditional_with_function_trick()
{
    return fa() == fb();
}

这是集会:

biconditional_with_function():
        sub     rsp, 8
        call    fa()
        test    al, al
        je      .L7
        call    fb()
        test    al, al
        jne     .L10
.L7:
        call    fa()
        mov     edx, eax
        xor     eax, eax
        test    dl, dl
        je      .L14
.L10:
        add     rsp, 8
        ret
.L14:
        call    fb()
        add     rsp, 8
        xor     eax, 1
        ret
biconditional_with_function_trick():
        push    rbx
        call    fa()
        mov     ebx, eax
        call    fb()
        cmp     bl, al
        pop     rbx
        sete    al
        ret

如果前半部分为真,您可以看到为biconditional_with_function生成的代码使用跳转跳过表达式的后半部分。有趣的是,当评估后半部分时,fa()fb()被整体调用两次,因为编译器不知道它们是否总是返回相同的结果。如果是这种情况,则应通过将评估结果保存在自己的变量中来重写代码:

bool biconditional_with_function_rewritten()
{
    bool a = fa();
    bool b = fb();
    return (a && b) || (!a && !b);
}

大会:

biconditional_with_function_rewritten():
        push    rbx
        call    fa()
        mov     ebx, eax
        call    fb()
        xor     eax, 1
        xor     eax, ebx
        pop     rbx
        ret

我们可以看到它们几乎相同,只剩下1个指令差异,使“技巧”方法略有优势。

对于逆向非重复,我们可以看到GCC在使用逻辑运算符时确实会避免评估第二个运算符,但在使用<运算符时却不会:

bool fa();
bool fb();

bool converse_nonimplication()
{
    return !fa() && fb();
}

bool converse_nonimplication_trick()
{
    return fa() < fb();
}

大会:

converse_nonimplication():
        sub     rsp, 8
        call    fa()
        test    al, al
        je      .L5
        xor     eax, eax
        add     rsp, 8
        ret
.L5:
        add     rsp, 8
        jmp     fb()
converse_nonimplication_trick():
        push    rbx
        call    fa()
        mov     ebx, eax
        call    fb()
        cmp     al, bl
        pop     rbx
        seta    al
        ret