这是一位高级经理在面试时问的问题。

哪个更快?

while(1) {
    // Some code
}

or

while(2) {
    //Some code
}

我说过两者具有相同的执行速度,因为while中的表达式最终应求值为true或false。在这种情况下,两者都求值为true,并且while条件中没有额外的条件指令。因此,两者将具有相同的执行速度,我更喜欢while(1)。

但面试官自信地说: “检查你的基本知识。While(1)比While(2)快。” (他不是在试探我的信心)

这是真的吗?

请参见:“for(;;)”是否比“while (TRUE)”快?如果不是,人们为什么要使用它?


当前回答

是的,对于人类来说,while(1)比while(2)快得多!如果我在一个不熟悉的代码库中看到while(1),我立即知道作者的意图,我的眼球可以继续看下一行。

If I see while(2), I'll probably halt in my tracks and try to figure out why the author didn't write while(1). Did the author's finger slip on the keyboard? Do the maintainers of this codebase use while(n) as an obscure commenting mechanism to make loops look different? Is it a crude workaround for a spurious warning in some broken static analysis tool? Or is this a clue that I'm reading generated code? Is it a bug resulting from an ill-advised find-and-replace-all, or a bad merge, or a cosmic ray? Maybe this line of code is supposed to do something dramatically different. Maybe it was supposed to read while(w) or while(x2). I'd better find the author in the file's history and send them a "WTF" email... and now I've broken my mental context. The while(2) might consume several minutes of my time, when while(1) would have taken a fraction of a second!

我有点夸张,但也只是一点点。代码的可读性非常重要。这一点在面试中很值得一提!

其他回答

我能想到为什么while(2)会更慢的唯一原因是:

代码将循环优化为 2 . CMP eax 当减法发生时,你实际上是在减法 A. 00000000 - 00000010 CMP eax, 2 而不是 B. 00000000 - 00000001 CMP eax, 1

CMP只设置标志,不设置结果。所以对于最低有效位,我们知道b是否需要借位,而对于a,你必须做两次减法才能得到借位。

这两个循环都是无限的,但我们可以看到哪个循环每次迭代需要更多的指令/资源。

使用gcc,我编译了以下两个程序,以不同的优化级别进行汇编:

int main(void) {
    while(1) {}
    return 0;
}

int main(void) {
    while(2) {}
    return 0;
}

即使没有优化(-O0),两个程序生成的程序集也是相同的。因此,两个循环之间没有速度差异。

作为参考,下面是生成的程序集(使用gcc main.c -S -masm=intel并带有优化标志):

o0:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    push    rbp
    .seh_pushreg    rbp
    mov rbp, rsp
    .seh_setframe   rbp, 0
    sub rsp, 32
    .seh_stackalloc 32
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

o1群:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

-O2和-O3(相同输出):

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 4,,15
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

事实上,为循环生成的程序集对于每个级别的优化都是相同的:

 .L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

重要的部分是:

.L2:
    jmp .L2

我不太懂汇编,但这显然是一个无条件循环。jmp指令无条件地将程序重置回. l2标签,甚至不将值与true进行比较,当然,它会立即再次这样做,直到程序以某种方式结束。这直接对应于C/ c++代码:

L2:
    goto L2;

编辑:

有趣的是,即使没有优化,下面的循环都在汇编中产生完全相同的输出(无条件jmp):

while(42) {}

while(1==1) {}

while(2==2) {}

while(4<7) {}

while(3==3 && 4==4) {}

while(8-9 < 0) {}

while(4.3 * 3e4 >= 2 << 6) {}

while(-0.1 + 02) {}

甚至令我惊讶的是:

#include<math.h>

while(sqrt(7)) {}

while(hypot(3,4)) {}

对于用户定义函数,事情变得更加有趣:

int x(void) {
    return 1;
}

while(x()) {}

#include<math.h>

double x(void) {
    return sqrt(7);
}

while(x()) {}

At -O0, these two examples actually call x and perform a comparison for each iteration.

First example (returning 1):

.L4:
    call    x
    testl   %eax, %eax
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Second example (returning sqrt(7)):

.L4:
    call    x
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jp  .L4
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

However, at -O1 and above, they both produce the same assembly as the previous examples (an unconditional jmp back to the preceding label).

TL;DR

Under GCC, the different loops are compiled to identical assembly. The compiler evaluates the constant values and doesn't bother performing any actual comparison.

The moral of the story is:

  • There exists a layer of translation between C source code and CPU instructions, and this layer has important implications for performance.
  • Therefore, performance cannot be evaluated by only looking at source code.
  • The compiler should be smart enough to optimize such trivial cases. Programmers should not waste their time thinking about them in the vast majority of cases.

也许面试官是故意问这个愚蠢的问题,想让你回答3点:

基本的推理。这两个循环都是无限的,很难谈论性能。 关于优化级别的知识。他想听听你的意见,如果你让编译器为你做任何优化,它会优化条件,特别是如果块不是空的。 了解微处理器架构。大多数架构都有一个特殊的CPU指令用于与0进行比较(但不一定更快)。

因为回答这个问题的人希望得到最快的循环,所以我的回答是,两者都是同样地编译成相同的汇编代码,正如在其他答案中所述。不过,你可以用“循环展开”向面试官建议;一个do {} while循环而不是while循环。

谨慎:您需要确保循环至少总是运行一次。

循环内部应该有一个中断条件。

同样,对于这种循环,我个人更喜欢使用do {} while(42),因为除了0之外,任何整数都可以完成这项工作。

当然,我不知道这位经理的真实意图,但我提出了一个完全不同的观点:当一个团队雇佣一个新成员时,了解他如何应对冲突情况是很有用的。

他们让你陷入冲突。如果这是真的,他们很聪明,这个问题也很好。对于某些行业,比如银行业,将你的问题发布到Stack Overflow可能会成为被拒绝的理由。

但我当然不知道,我只是提出一个选择。