如果(flag == 0)或(0 == flag),哪一个执行得更快?


111

面试问题:哪个执行速度更快,if (flag==0)或者if (0==flag)?为什么?


330
被提名为有史以来最愚蠢的面试问题。竞争激烈。
康拉德·鲁道夫

119
您:指出一种可能值得困扰两者之间的差异的情况。采访者:好的,你被录用了。
克里斯·卢茨

37
两者之间的唯一区别是,使用更高版本的约定,您可以if(flag = 0)以一些易读性为代价来确保避免出现bug 。
Amarghosh,2011年

22
@Amarghosh:以使您的代码难以阅读和不直观为代价。双赢时使用前者来打开编译器警告。
GManNickG 2011年

129
一位编译器作家在接受采访时就提到了这一点。他低声回答:“ 想快点哪个?”。

Answers:


236

我还没有看到任何正确的答案(并且已经有一些警告):Nawaz确实指出了用户定义的trap。而且我很遗憾我仓促对“最愚蠢的问题”投了赞成票,因为似乎很多人都没有把它弄对,并且它为进行有关编译器优化的美好讨论提供了空间:)

答案是:

什么是flag类型?

如果flag实际上是用户定义的类型。然后,取决于operator==选择哪个过载。当然,它们不是对称的,这似乎很愚蠢,但这是允许的,而且我已经看到了其他滥用情况。

如果flag是内置的,则两者应采用相同的速度。

Wikipedia上的文章x86,我押注Jxxif语句的指示信息:可能是JNZ(如果不是零,则跳转)或等效形式。

我怀疑即使关闭了优化功能,编译器也会错过如此明显的优化功能。这是为“ 窥孔优化”设计的。

编辑:再次出现,所以让我们添加一些程序集(LLVM 2.7 IR)

int regular(int c) {
  if (c == 0) { return 0; }
  return 1;
}

int yoda(int c) {
  if (0 == c) { return 0; }
  return 1;
}

define i32 @regular(i32 %c) nounwind readnone {
entry:
  %not. = icmp ne i32 %c, 0                       ; <i1> [#uses=1]
  %.0 = zext i1 %not. to i32                      ; <i32> [#uses=1]
  ret i32 %.0
}

define i32 @yoda(i32 %c) nounwind readnone {
entry:
  %not. = icmp ne i32 %c, 0                       ; <i1> [#uses=1]
  %.0 = zext i1 %not. to i32                      ; <i32> [#uses=1]
  ret i32 %.0
}

即使不知道如何阅读投资者关系,我也认为这是不言而喻的。


4
@Matthieu:您说我还没有看到任何正确的答案 ..但是我的回答是正确的,我认为:P
Nawaz

7
好!您的答案可能将“最愚蠢的问题”变成“最棘手的问题/最严重的问题”。“让我们为候选人挖一个洞,看看他是否掉进去了……” :)我想我们所有人都会自动假定flag必须为整数或布尔值。OTOH,具有flag用户定义类型的变量名称本身是非常错误的,恕我直言
davka 2011年

@Nawaz:我可能已经跳过了答案的最后一段:p
Matthieu M.

1
@Nawaz:我不是真正的种族,我通常在回答问题后很久才读问题,人们往往只阅读最喜欢的第一个答案:)但是我实际上是在阅读有关编译器优化的内容,这让我很震惊琐碎优化的典型案例,所以我想为那些真正烦恼的读者指出这一点……我很惊讶,实际上我得到了如此多的赞誉。现在,这是我投票最多的答案,尽管它肯定不是我付出最大努力的答案:/无论如何,我编辑了答案并纠正了我的说法:)
Matthieu M.11年

2
@mr_eclair:内置类型是该语言中内置的类型(顾名思义)。也就是说,即使没有单个#include指令也可以使用它。为简单起见,通常达intcharbool等。所有其他类型被认为是用户定义的,那就是它们的存在,因为他们有一些用户他们宣称的结果:typedefenumstructclass。例如,std::string是用户定义的,即使您自己当然也没有定义它:)
Matthieu M.

56

与GCC 4.1.2相同的amd64代码:

        .loc 1 4 0  # int f = argc;
        movl    -20(%rbp), %eax
        movl    %eax, -4(%rbp)
        .loc 1 6 0 # if( f == 0 ) {
        cmpl    $0, -4(%rbp)
        jne     .L2
        .loc 1 7 0 # return 0;
        movl    $0, -36(%rbp)
        jmp     .L4
        .loc 1 8 0 # }
 .L2:
        .loc 1 10 0 # if( 0 == f ) {
        cmpl    $0, -4(%rbp)
        jne     .L5
        .loc 1 11 0 # return 1;
        movl    $1, -36(%rbp)
        jmp     .L4
        .loc 1 12 0 # }
 .L5:
        .loc 1 14 0 # return 2;
        movl    $2, -36(%rbp)
 .L4:
        movl    -36(%rbp), %eax
        .loc 1 15 0 # }
        leave
        ret

18
+1可以证明编译器的优化是相同的。
k rey

56

您的版本不会有任何差异。

我假设typeof标志不是用户定义的类型,而是某种内置类型。枚举是例外!。您可以将枚举视为内置枚举。实际上,它的值是内置类型之一!

如果是用户定义的类型(除外enum),那么答案完全取决于您如何重载operator ==。请注意,您必须==通过定义两个函数来重载,每个函数一个!


8
这可能是问这个问题的唯一可能的原因,恕我直言
davka 2011年

15
如果现代编译器错过如此明显的优化,我将感到非常惊讶。
Pedro d'Aquino

3
据我所知 !不是按位操作
Xavier Combelle 2011年

8
@Nawaz:没有投票,但您的回答实际上是错误的,仍然有如此多的投票真是太恐怖了。作为记录,将整数与0进行比较是一条汇编指令,与否定完全相同。实际上,如果编译器有些愚蠢,那么这甚至可能比求反要(虽然不太可能)。
Konrad Rudolph

6
@Nawaz:说它可以,将会或通常会更快,这仍然是错误的。如果存在差异,则“与零比较”的版本会更快,因为取反一实际上转换为两个运算:“取反操作数;检查结果是否为非零”。当然,实际上,编译器会对其进行优化以产生与普通的“与零比较”版本相同的代码,但是该优化将应用于否定版本,以使其赶上,而不是相反。康拉德是对的。
jalf

27

绝对没有区别。

不过,您可以参考消除工作分配/比较错别字的方式来回答面试问题,从而获得积分,但是:

if (flag = 0)  // typo here
   {
   // code never executes
   }

if (0 = flag) // typo and syntactic error -> compiler complains
   {
   // ...
   }

的确,例如C编译器确实对前者(flag = 0)发出警告,但PHP,Perl或Javascript或中没有此类警告<insert language here>


@Matthieu Huh。我一定错过了描述“适当的”支撑样式的meta文章。
莱纳斯·克莱恩

7
我根本没有投票,但是有什么意义:为什么人们在每次投票时都要自我解释为何如此重要?选票是匿名设计的。我完全反对下注者应始终发表评论的想法,因为我个人不希望仅因为我留下指出问题的评论而被假定为下注者。也许下降投票者认为大多数答案与速度问题无关?也许他认为这鼓励了他不赞成的编码风格?也许他是个鸡巴,并希望自己的回答获得最高评分?
David Hedlund

3
不论原因如何,人们都应自由投票。从名誉角度来看,这几乎总是一件好事,因为它经常会激起其他人的投票,以反对不当之选,而实际上,一个单独的投票将抵消五次不当之选。
David Hedlund

26
@David:投票者应该自我解释,因为该网站不是关于秘密人气投票,匿名投票等。这个网站是关于学习的。如果有人通过拒绝投票说出一个不正确的回答,那么拒绝投票的人如果不解释原因,就会以自己的知识自私。他们愿意在正确的时候承担全部功劳,但是在他人的错误时不愿意分享知识。
John Dibling 2011年

1
只是为了消除支撑风格的问题,我真的以为Matthieu只是想开个玩笑。看到有人根据此类问题进行投票,我会感到惊讶。话虽如此,并非所有人都以完全相同的方式使用投票。我可以看到拒绝投票的理由,因为该帖子似乎鼓吹选民可能不赞成的编码风格(请注意倡导编码风格之间的区别-“如果您这样编写代码,则在编写代码时会出现编译器错误这种错字”-并仅使用一种编码样式(例如大括号)就可以了
David Hedlund

16

速度方面绝对没有区别。为什么要有?


7
如果编译器被完全延迟。那是唯一的原因。
JeremyP 2011年

@JeremyP:即使编译器被延迟,我也无法想象有什么不同。据我所知,编译器作者必须故意这样做。
乔恩

2
假设处理器有一条“测试是否为0”指令,x == 0可以使用它,但0 == x可以使用普通比较。我的确曾说过,这将不得不加以限制。
JeremyP,2011年

8
如果flag是用户定义的类型,且具有operator ==()的不对称重载
OrangeDog 2011年

因为我们可能具有virtual operator==(int)用户定义的类型?
洛罗

12

那么flag是用户定义的类型时会有区别

struct sInt
{
    sInt( int i ) : wrappedInt(i)
    {
        std::cout << "ctor called" << std::endl;
    }

    operator int()
    {
        std::cout << "operator int()" << std::endl;
        return wrappedInt;
    }

    bool operator==(int nComp)
    {
        std::cout << "bool operator==(int nComp)" << std::endl;
        return (nComp == wrappedInt);
    }

    int wrappedInt;
};

int 
_tmain(int argc, _TCHAR* argv[])
{
    sInt s(0);

    //in this case this will probably be faster
    if ( 0 == s )
    {
        std::cout << "equal" << std::endl;
    }

    if ( s == 0 )
    {
        std::cout << "equal" << std::endl;
    }
}

在第一种情况下(0 == s),将调用转换运算符,然后将返回的结果与0进行比较。在第二种情况下,将调用==运算符。


3
+1表示转换运算符可能与运算符==一样重要。
Tony Delroy

11

如有疑问,请对其进行基准测试并了解事实。


2
基准测试有什么问题?有时实践比理论
更能

1
这就是我开始阅读此主题时要寻找的答案。理论似乎比实践更具吸引力,可以寻找答案和支持:)
Samuel Rivas

他如何在面试时进行基准测试?另外,我认为面试官甚至不知道基准测试意味着什么,因此他可能会被冒犯。
IAdapter 2011年

他们对问题(IMO)的正确回答是:“这在很大程度上取决于编译器和程序的其余部分。我会编写一个基准测试并在5分钟内对其进行测试”
Samuel Rivas

7

它们在速度方面应该完全相同。

但是请注意,有人在等式比较(通常称为“ Yoda条件”)中将常数放在左侧,以避免编写=(赋值运算符)而不是==(等式比较运算符)时可能出现的所有错误。由于分配给文字会触发编译错误,因此可以避免这种错误。

if(flag=0) // <--- typo: = instead of ==; flag is now set to 0
{
    // this is never executed
}

if(0=flag) // <--- compiler error, cannot assign value to literal
{

}

另一方面,大多数人发现“ Yoda条件”看起来很奇怪和烦人,特别是因为使用适当的编译器警告也可以发现他们防止的错误类别。

if(flag=0) // <--- warning: assignment in conditional expression
{

}

感谢您的回应。但是请注意,例如,如果有条件赋值,PHP不会发出警告。
莱纳斯·克莱恩

5

正如其他人所说,没有区别。

0必须进行评估。flag必须进行评估。无论放置在哪一侧,此过程都需要相同的时间。

正确的答案是:它们的速度相同。

即使是表达式if(flag==0)if(0==flag)也具有相同数量的字符!如果其中之一写为if(flag== 0),则编译器将有一个额外的空间来解析,因此您有合理的理由指出编译时间。

但是,由于没有这样的事情,因此绝对没有理由要比另一个更快。如果有原因,那么编译器会对生成的代码做一些非常非常奇怪的事情...


5

哪种速度取决于您使用的==版本。这是一个使用==的2种可能实现的代码段,具体取决于您是选择调用x == 0还是0 == x来选择其中2种。

如果您只是使用POD,那么就速度而言就没关系了。

#include <iostream>
using namespace std;

class x { 
  public:
  bool operator==(int x) { cout << "hello\n"; return 0; }
  friend bool operator==(int x, const x& a) { cout << "world\n"; return 0; } 
};

int main()
{ 
   x x1;
   //int m = 0;
   int k = (x1 == 0);
   int j = (0 == x1);
}

5

好吧,出于练习的缘故,我完全同意在给OP的评论中所说的所有内容:

如果编译器不够聪明(实际上,您不应使用它)或禁用了优化,则x == 0可以编译为本机汇编jump if zero指令,而0 == x对数值的比较可能是更通用(且成本更高)的比较。

尽管如此,我还是不想为一个以这种方式思考的老板工作...



3

我认为最好的答案是“此示例使用哪种语言”?

该问题未指定语言,并且标记为“ C”和“ C ++”。准确的答案需要更多信息。

这是一个糟糕的编程问题,但是在曲折的“让被采访者有足够的绳索去上吊自己或建立秋千”部门中,这可能是一个好选择。这类问题的问题是,他们通常会被写下来,并从面试官传给面试官,直到遇到那些从各个角度都不真正了解它的人。


3

使用建议的方法构建两个简单的程序。

汇编代码。查看程序集,您可以判断,但是我怀疑是否有区别!

面试越来越少。


2

顺便说一句(我实际上认为,任何体面的编译器都会解决这个问题,因为它会对其进行优化)使用0 == flag over flag == 0确实可以防止错字而忘记了其中一个=(即,如果您不小心键入了flag = 0将会编译,但是0 = flag不会编译),我认为这是每个人都在某一点或另一点犯的错误...


0

如果根本没有区别,是什么阻止编译器一次选择更快的速度?因此,从逻辑上讲,没有任何区别。大概这就是面试官的期望。这实际上是一个绝妙的问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.