C++ 条件运算符的性能表现

3
我有一个条件语句expensive_foo(),在99.9%的情况下为false。我还有一个条件语句bar,在大约50%的情况下为true。

如果这两个语句都为true,我想执行一些操作。因此,我几乎可以确定expensive_foo()为false,并且只有在bar为true时才想检查它。

下面的代码会仅在bar为true时检查expensive_foo()吗?还是每次都会检查expensive_foo()

if ( bar && expensive_foo() )
{
    ...
}

或者我需要制作一个像这样的结构:
if ( bar )
{
    if ( expensive_foo() )
    {
        ...
    }
}

7
逻辑布尔运算符短路,除非被重载,也就是说,只有当“bar”为真时,“foo”才会被检查,即“是的”。尽管如此,我必须承认,我并没有看到从节省一个布尔比较中获得的性能优势。 - Xeo
2
嗯,把那个加入问题中会很聪明。 - Xeo
2
那么,你是使用 bar && heavy_method_yielding_foo() 还是 foo = heavy_method(); if(bar && foo) ... - Xeo
3
这些是重要的细节,如果没有这些,回答问题基本上是不可能的(或者得到的答案可能不是你真正想要的)。 - Xeo
1
因为在你的编辑之后,它不再适用了。然后你又进行了编辑。 - Luchian Grigore
显示剩余9条评论
5个回答

5
逻辑 AND 运算符 && 是短路的,这意味着只有在第一个操作数被评估为 true 时才保证评估第二个操作数。条件语句 if (bar && foo)if (bar) if (foo) 是相同的。
由于计算 foo 的成本很高,你几乎肯定应该先检查 bar。即使分支预测器无法很好地预测 bar,与计算 foo 相比,这只是一个次要的影响。
因此,结构应该是这样的:
if (bar)
{
    if (bool const foo = compute())   // expensive
    {
        // ...
    }
}

请注意,所有这些结构都意味着我们可能会或可能不会调用compute()。如果您需要无条件地调用该函数,则应将其作为第一个检查。在这种情况下,应利用结果的成功分支预测。

这只在语义上是正确的,而不是在性能方面。如果bar和foo没有副作用,编译器仍然可以重新排序它们,从而导致性能损失。 - usr
@usr:我非常确定它不能。 - Puppy
@DeadMG 编译器可以执行不改变程序行为标准的任何操作。如果没有可观察到的行为变化,编译器就可以这样做。(不过,原帖已经被编辑,告诉我们 foo 实际上是 foo_expensive()。这使得我的观点无效,尽管它是正确的)。 - usr
@DeadMG 这要看情况。如果它们具有副作用,编译器无法重新排序这些副作用。否则,您将无法观察到任何区别。 - R. Martinho Fernandes
@Kolyunya:不是的。if (bar && compute())if (bar) { if (compute()) { /* .. */ } } 是完全相同的,很安全可靠。我只是想让你存储一下 compute() 的结果,或者加上一个 else 分支。但如果你不需要的话,随意将所有内容放到一个条件语句中即可。 - Kerrek SB
显示剩余3条评论

2

只有在bar为true的情况下,两个代码才会检查foo。在第一种情况下,这是由于语言懒惰的布尔表达式评估所保证的(实际上,某些编译器可以明确地关闭它,但默认情况下是开启的)。


4
如果有任何编译器允许你关闭短路求值,那会让我感到惊讶,因为这会破坏很多代码。 - Mike Seymour
你可以通过说“bar & foo”来关闭它。 - usr

1

我看到了一个评论,说编译器可以重新排列操作,我倾向于这种观点。改进顺序程序的自动并行化的努力已经带来了一些更好的编译器,并且它可以帮助程序员避免在程序中进行复杂的并行化编写。

如果重新排序不违反/恶化局部性或在广义上不违反程序实际意图,那么编译器就可以重新排序操作。

但我怀疑编译器是否会尝试重新排序像以下这样的条件语句:

  if ( bar && expensive_foo() )

如果将价格较低的布尔函数放在开头,我们肯定可以节省一些时间。


1
其他答案都很好,但我对前提有疑问。
如果您查询的谓词99.9%的时间是错误的,那就相当于在2000个条目的列表中进行线性搜索,您要查找的项可能随机分布在任何位置。
在这种情况下,通常会尝试以不同的方式组织列表,以便进行O(logN)(或更好)的搜索,而不是O(N)。 我不知道您是否可以这样做,但这是我关注的重点。
当一个问题具有高度倾斜的概率时,如果可以消除偏差,那么这可能是一个大的加速机会。 (假设在此活动中花费了大量时间。如果没有,那么这一点是无意义的。)

1

如果两者的优化汇编代码不完全相同,我会感到惊讶。只有当bar为false时,expensive_foo()才不会被调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接