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

哪个更快?

while(1) {
    // Some code
}

or

while(2) {
    //Some code
}

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

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

这是真的吗?

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


当前回答

我认为线索可以从“高级经理的询问”中找到。这个人显然在成为经理后就停止了编程,然后他/她花了几年时间才成为高级经理。从未失去对编程的兴趣,但从那以后再也没有写过一行字。所以他提到的并不是“任何一个像样的编译器”,而是“这个人在二三十年前合作过的编译器”。

At that time, programmers spent a considerable percentage of their time trying out various methods for making their code faster and more efficient as CPU time of 'the central minicomputer' was so valueable. As did people writing compilers. I'm guessing that the one-and-only compiler his company made available at that time optimized on the basis of 'frequently encountered statements that can be optimized' and took a bit of a shortcut when encountering a while(1) and evaluated everything else, including a while(2). Having had such an experience could explain his position and his confidence in it.

让你被录用的最好方法可能是,在你顺利地引导他进入下一个面试主题之前,让高级经理忘乎所以地给你讲2-3分钟“编程的美好时光”。(好的时机在这里很重要——太快你会打断故事——太慢你会被贴上焦点不集中的标签)。一定要在面试结束时告诉他,你对这个话题非常感兴趣。

其他回答

我曾经用C语言和汇编代码编写程序,那时候这类废话可能会有很大不同。当它确实起作用时,我们在汇编中写了出来。

如果我被问到这个问题,我会重复唐纳德·克努特1974年关于过早优化的名言,如果面试官没有笑,我就会走开,然后继续前进。

等一下。面试官长得像这个人吗?

面试官自己没能通过面试已经够糟糕的了, 如果这家公司的其他程序员已经“通过”了这个测试呢?

不。求值语句1 == 0和2 == 0应该同样快。我们可以想象,在糟糕的编译器实现中,一个可能比另一个快。但是,没有充分的理由说明其中一个应该比另一个快。

即使在某些模糊的情况下,这种说法是正确的,也不应该基于模糊(在这种情况下,令人毛骨悚然)琐事的知识来评估程序员。不要担心这次面试,最好的办法就是走开。

免责声明:这不是原创呆伯特漫画。这只是一个混搭。

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

使用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.

对这个问题最有可能的解释是,面试官认为处理器会逐一检查数字的每一位,直到它达到一个非零值:

1 = 00000001
2 = 00000010

如果“is 0 ?”算法从数字的右侧开始,并且必须检查每一位直到它达到非零位,那么while(1){}循环每次迭代必须检查的比特数是while(2){}循环的两倍。

这需要一个关于计算机如何工作的非常错误的思维模型,但它确实有自己的内部逻辑。一种检查方法是询问while(-1){}或while(3){}是否同样快,或者while(32){}是否更慢。

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

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

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

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