ChatGPT解决这个技术问题 Extra ChatGPT

哪个更快:while(1) 或 while(2)?

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

哪个更快?

while(1) {
    // Some code
}

或者

while(2) {
    //Some code
}

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

但面试官自信地说:“检查你的基础。while(1)while(2)快。” (他不是在考验我的信心)

这是真的?

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

一个半体面的编译器会将这两种形式都优化为无。
在每个 while(n) 的优化构建中,n != 0 或 for(;;) 将被转换为 Assembly 无限循环,开头带有标签,最后带有 goto。完全相同的代码,相同的性能。
毫不奇怪,库存优化会为 both 片段带来 0x100000f90: jmp 0x100000f90(地址不同,显然)。面试官可能在注册测试与简单的标记跳跃之间对冲。这个问题和他们的假设都是蹩脚的。
面试官提出的这个问题与 dilbert.com/strips/comic/1995-11-17 属于同一主持 - 你会遇到一个真正相信他们所说的话的人,而不管他们陈述中的愚蠢商数。只需从以下选项中进行选择:深呼吸、发誓、大笑、哭泣、以上几种组合 :)
@Mike W:人们可能想知道编译器应该做什么:转换为 Halt 语句,或者考虑循环在无限时间后退出并优化无限延迟?

C
Community

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

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

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

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

即使没有优化 (-O0),the generated assembly was identical for both programs因此,两个循环之间没有速度差异。

作为参考,这里是生成的程序集(使用带有优化标志的 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()) {}

-O0,这两个示例实际调用 x 并对每次迭代执行比较。

第一个例子(返回 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"

第二个示例(返回 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"

但是,在 -O1 及以上,它们都生成与前面示例相同的程序集(无条件 jmp 返回到前面的标签)。

TL;博士

在 GCC 下,不同的循环被编译成相同的程序集。编译器评估常量值并且不打扰执行任何实际比较。

这个故事的寓意是:

C++ 源代码和 CPU 指令之间存在一层转换,这一层对性能有重要影响。

因此,不能仅通过查看源代码来评估性能。

编译器应该足够聪明以优化这些琐碎的情况。在绝大多数情况下,程序员不应该浪费时间去思考它们。


也许面试官没有使用 gcc
@Matt McNabb 这是一个很好的观点,但是如果面试官依赖于编译器特定的优化,那么他们需要在他们的问题中非常明确地说明这一点,并且他们需要接受“没有区别”的答案是正确的一些(大多数?)编译器。
为了消除任何疑问,我在 clang 3.4.2 中对此进行了测试,两个循环在每个 -O 级别产生相同的程序集。
我一点也不觉得这令人惊讶,因为您放置在循环条件部分的所有内容都是编译时常量。因此,我怀疑编译器会看到它们的循环将始终为真或假,或者分别简单地 jmp 回到开头或完全删除循环。
@hippietrail 我看不出循环的内容(或缺少此内容)如何影响这些优化(任何 break 语句的可能性除外),但我还是测试了它,没有,即使循环内有代码对于 while(1)while(2),跳转是绝对且无条件的。如果您真的担心,请随意测试其他人。
C
Chris Culter

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

如果我看到 while(2),我可能会停下来,试图找出作者为什么不写 while(1)。作者的手指在键盘上打滑了吗?这个代码库的维护者是否使用 while(n) 作为一种晦涩的注释机制来使循环看起来不同?对于某些损坏的静态分析工具中的虚假警告,这是一种粗略的解决方法吗?或者这是我正在阅读生成的代码的线索?它是由不明智的查找并替换所有、错误的合并或宇宙射线导致的错误吗?也许这行代码应该做一些截然不同的事情。也许它应该是 while(w)while(x2)。我最好在文件的历史记录中找到作者,然后给他们发一封“WTF”电子邮件……现在我已经打破了我的心理背景。 while(2) 可能会占用我几分钟的时间,而 while(1) 可能只需要几分之一秒!

我夸大了,但只是一点点。代码可读性真的很重要。这在采访中值得一提!


当然,这一点也不夸张。肯定会在这里使用 svn-annotate git blame(或其他),并且通常需要几分钟来加载文件责备历史记录。然后终于下定决心“啊我明白了,作者高中刚毕业时写的这句话”,就浪费了10分钟……
投了反对票,因为我厌倦了惯例。开发者有这个机会写一个他喜欢的数字,然后一个无聊的人来唠叨为什么它不是 1
由于言辞而被否决。读取 while(1) 的速度与 while(2) 的速度完全相同。
一分钟前,当我在代码中看到 while(2) 时,我经历了与此答案中描述的完全相同的心理过程(考虑到“此代码库的维护者是否使用 while(n) 作为一种晦涩的注释机制来使循环看起来不同的?”)。所以不,你没有夸大其词!
@chqrlie Emacs 默认值不合适。无论如何,虽然 (true) 比它们都快:)
K
Keith Thompson

显示特定编译器为具有特定选项集的特定目标生成的代码的现有答案并不能完全回答问题 - 除非在特定上下文中提出问题(“使用 gcc 4.7.2 for x86_64 哪个更快使用默认选项?”,例如)。

就语言定义而言,在抽象机中,while (1) 计算整数常量 1,而 while (2) 计算整数常量 2;在这两种情况下,都会比较结果是否等于零。语言标准完全没有说明这两种结构的相对性能。

我可以想象一个极其幼稚的编译器可能会为这两种形式生成不同的机器代码,至少在没有请求优化的情况下编译时是这样。

另一方面,C 编译器绝对必须在编译时评估一些常量表达式,当它们出现在需要常量表达式的上下文中时。例如,这个:

int n = 4;
switch (n) {
    case 2+2: break;
    case 4:   break;
}

需要诊断;惰性编译器没有将 2+2 的评估推迟到执行时间的选项。由于编译器必须具有在编译时评估常量表达式的能力,因此没有充分的理由不利用这种能力,即使它不是必需的。

C 标准 (N1570 6.8.5p4) 说

迭代语句会导致重复执行称为循环体的语句,直到控制表达式比较等于 0。

因此相关的常量表达式是 1 == 02 == 0,它们的计算结果都是 int0。 (这些比较隐含在 while 循环的语义中;它们不作为实际的 C 表达式存在。)

一个天真的编译器可能为这两种结构生成不同的代码。例如,对于第一个,它可以生成一个无条件的无限循环(将 1 视为一种特殊情况),对于第二个,它可以生成一个等效于 2 != 0 的显式运行时比较。但是我从来没有遇到过实际上会以这种方式运行的 C 编译器,而且我严重怀疑是否存在这样的编译器。

大多数编译器(我想说所有生产质量的编译器)都有请求额外优化的选项。在这样的选择下,任何编译器都不太可能为这两种形式生成不同的代码。

如果您的编译器为这两个结构生成不同的代码,首先检查不同的代码序列是否实际上具有不同的性能。如果是这样,请尝试使用优化选项(如果可用)再次编译。如果它们仍然不同,请向编译器供应商提交错误报告。从未能符合 C 标准的意义上说,这不是(必然)一个错误,但几乎可以肯定它是一个应该纠正的问题。

底线:while (1)while(2)几乎当然具有相同的性能。它们具有完全相同的语义,任何编译器都没有充分的理由不生成相同的代码。

尽管编译器为 while(1) 生成比 while(2) 更快的代码是完全合法的,但编译器为 while(1) 生成比在同一程序中再次出现 while(1) 更快的代码同样合法。

(您问的问题中隐含了另一个问题:您如何处理坚持不正确技术观点的面试官。这可能是 the Workplace site 的一个好问题)。


“在这种情况下,相关的(隐式)常量表达式是 1 != 0 和 2 != 0,它们的计算结果都是 int 值 1”......这过于复杂且不准确。标准只是说 while 的控制表达式必须是标量类型,并且循环体重复直到表达式比较等于 0。它并没有说有一个隐式 expr != 0 被评估......这将需要将结果 - 0 或 1 - 依次与 0 进行比较,无穷无尽。不,表达式与 0 进行比较,但该比较不会产生值。 PS我投了赞成票。
@JimBalter:我明白你的意思,我会更新我的答案来解决这个问题。不过,我的意思是,标准的措辞“......直到控制表达式比较等于 0”意味着评估 <expr> == 0;这就是 C 中“比较等于 0”意味着 的含义。这种比较是 while 循环语义的一部分。无论是在标准中还是在我的答案中,都没有暗示需要再次将结果与 0 进行比较。 (我应该写 == 而不是 !=。)但是我的那部分答案不清楚,我会更新它。
“”比较等于 0” 表示在 C 中”——但该语言在 标准 中,而不是在 C 中……实现进行比较,它不会生成 C 语言比较。您写“两者都评估为 int 值 1”——但从未发生过这样的评估。如果您查看由最天真、未优化的编译器为 while (2)while (pointer)while (9.67) 生成的代码,您将看不到任何生成 01 的代码。 “我应该写 == 而不是 !=" - 不,这没有任何意义。
@JimBalter:嗯。我并不是说“比较等于 0”意味着存在 ... == 0 C 表达式。我的观点是,标准对 while 循环的描述所要求的“比较等于 0”和明确的 x == 0 表达式在逻辑上都暗示了相同的操作。而且我认为一个痛苦天真的 C 编译器可能会为任何 while 循环生成 int01 的代码——尽管我不相信任何实际的编译器就是这么幼稚。
注意:这已经是一个 The Workplace 问题:workplace.stackexchange.com/questions/4314/…
j
janos

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

https://i.stack.imgur.com/L6P1H.png

面试官自己这次面试失败已经够惨了,万一这家公司的其他程序员都“通过”了这个测试呢?

不会。评估语句 1 == 02 == 0 应该同样快。我们可以想象糟糕的编译器实现,其中一个可能比另一个更快。但是没有好的理由为什么一个应该比另一个更快。

即使在某些晦涩的情况下,该声明是正确的,也不应根据对晦涩(在这种情况下,令人毛骨悚然)琐事的知识来评估程序员。不用担心这次采访,这里最好的举动是走开。

免责声明:这不是原版呆伯特卡通。这只是一个 mashup


不,但实际上,我们都可以很容易地想象,所有认真的公司编写的编译器都会产生合理的代码。让我们以“非优化案例”/O0 为例,也许它最终会像 anatolyg 发布的那样。然后是 CPU 的问题,与 1 到 0 相比,cmp 操作数的运行周期是否比 2 到 0 少?一般执行 cmp 需要多少个周期?它是根据位模式变化的吗?它们是否更“复杂”的位模式会减慢 cmp ?我个人不知道。你可以想象一个超级白痴的实现从 0 到 n 逐位检查(例如 n=31)。
这也是我的观点:对于 1 和 200,cmp 操作数应该同样快。也许我们可以想象愚蠢的实现,但情况并非如此。但是我们能想象一个 非白痴 实现,其中 while(1)while(200) 快吗?同样,如果在某个史前时代,唯一可用的实现就是这样的愚蠢,我们今天应该对此大惊小怪吗?我不这么认为,这是尖头的老板谈话,而且是真正的宝石!
@v.ouddou“与 1 到 0 相比,与 2 到 0 相比, cmp 操作数的运行周期会更少” - 不,您应该了解什么是循环。 “我个人不知道。你可以想象一个超级白痴的实现,从 0 到 n 一点一点地检查”——或者反过来,仍然让面试官成为一个无能的白痴。为什么要担心一点一点地检查呢?实施可能是一个坐在盒子里的人,他决定在评估您的程序时午休。
根据 CPU 架构,将 0 或 1 加载到寄存器中可能比加载 2 更快。但是问这个问题的人显然没有意识到循环中的测试编译成什么都没有。
L
Lightness Races in Orbit

你的解释是正确的。这似乎是一个考验你除了技术知识之外的自信心的问题。

顺便说一句,如果你回答

两段代码同样快,因为两者都需要无限时间才能完成

面试官会说

但是虽然(1)每秒可以进行更多的迭代;你能解释一下为什么吗? (这是胡说八道;再次测试您的信心)

所以通过像你一样回答,你节省了一些时间,否则你会浪费在讨论这个糟糕的问题上。

这是我的系统(MS Visual Studio 2012)上的编译器生成的示例代码,优化关闭:

yyy:
    xor eax, eax
    cmp eax, 1     (or 2, depending on your code)
    je xxx
    jmp yyy
xxx:
    ...

打开优化后:

xxx:
    jmp xxx

所以生成的代码是完全一样的,至少使用优化编译器是一样的。


此代码实际上是编译器在我的系统上输出的内容。我没有弥补。
icepack “while 的操作数是布尔类型”——完全是胡说八道。你是面试官吗?我建议您在做出此类声明之前先熟悉 C 语言及其标准。
“我没编。” ——请不要理会icepack,他在胡说八道。 C 没有布尔类型(它在 stdbool.h 中确实有 _Bool,但这不一样,并且 while 的语义早在它之前)并且 while 的操作数不是布尔型或 _Bool 或任何其他特定类型. while 的操作数可以是 any 表达式 ... while 在 0 处中断并在非 0 处继续。
“两段代码都同样快,因为两者都需要无限时间才能完成”让我想到了一些有趣的事情。终止无限循环的唯一方法是让一个位被带电粒子翻转或硬件故障:将语句从 while (00000001) {} 更改为 while (00000000) {}。真实位越多,值翻转为假的机会就越小。可悲的是,2 也只有一个真正的位。但是,3 将运行更长的时间。这也仅适用于并不总是优化它的编译器(VC++)。
@mr5 不。为了稍微翻转以实际导致这样的事情,您正在谈论数万年的执行时间。只是一个思想实验。如果您来自不朽的种族,您可能希望使用 -1 来防止位翻转影响您的程序。
R
Ryan Cavanaugh

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

1 = 00000001
2 = 00000010

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

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


我假设面试官的误解更像是“2 是一个需要转换为布尔值才能用于条件表达式的 int,而 1 已经是布尔值”。
如果比较算法从左边开始,则相反。
+1,这正是我的想法。您完全可以想象有人相信,从根本上说,CPU 的 cmp 算法是线性位检查,在第一个差异时提前退出循环。
@PeterCordes “我从来没有听过任何人(除了你)争辩说 gcc 或 clang 的 -O0 输出不够字面意思。”我从来没有说过这样的话,我根本没有 gcc 或 clang 的想法。你一定是误读了我。我只是不认为 MSVC 产生的东西很有趣。
@PeterCordes我错了,在MSVC中,while(1)上的断点不会在循环之前中断,而是在表达式上。但是当优化表达式时,它仍然会在循环内崩溃。以 this code 多休息。在未优化的调试模式下,您会得到 this。优化时,many breakpoints fall together or even spill over into the next function(IDE 在调试时显示结果断点)- corresponding disassembly
P
Peter Mortensen

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

他们让你陷入冲突。如果这是真的,他们很聪明,问题也很好。对于某些行业,例如银行业,将您的问题发布到 Stack Overflow 可能是拒绝的原因。

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


它确实很棒,但是 while(2) 与 while(1) 显然是取自 dilbert 漫画。它不能由头脑正常的人发明(有人怎么想出 while(2) 作为可能写的东西?)。如果您的假设是正确的,那么您肯定会给出一个如此独特的问题,以至于您可以在谷歌上搜索它。就像“while(0xf00b442) 比 while(1) 慢”,否则银行如何找到被采访者的问题?你认为他们是 NSA 并且可以访问 keycore 吗?
O
OldFrank

我想线索可以在“一位高级经理的询问”中找到。这个人在当了经理后显然就停止了编程,然后他/她花了几年时间才成为高级经理。从未对编程失去兴趣,但从那时起从未写过一行。所以他的参考不是“任何像样的编译器”,正如一些答案所提到的,而是“这个人在 20-30 年前使用的编译器”。

那时,由于“中央小型机”的 CPU 时间非常宝贵,程序员们花了相当多的时间尝试各种方法来使他们的代码更快、更高效。就像编写编译器的人一样。我猜他的公司当时提供的唯一编译器基于“经常遇到的可以优化的语句”进行了优化,并在遇到一段时间(1)时采取了一些捷径并评估了一切否则,包括一段时间(2)。有过这样的经历可以解释他的立场和他对此的信心。

让你被录用的最好方法可能是让高级经理得意忘形,在你顺利引导他进入下一个面试主题之前,用 2-3 分钟就“编程的美好时光”向你讲授 2-3 分钟。 (好的时机在这里很重要——太快,你会打断故事——太慢,你会被贴上注意力不足的标签)。一定要在采访结束时告诉他,你非常有兴趣了解更多关于这个话题的信息。


R
Rad'Val

你应该问他是怎么得出这个结论的。在任何像样的编译器下,两者都编译为相同的 asm 指令。所以,他应该告诉你编译器也开始了。即便如此,您也必须非常了解编译器和平台,才能做出有理论根据的猜测。最后,在实践中这并不重要,因为还有其他外部因素,如内存碎片或系统负载,会比这个细节更能影响循环。


@GKFX 如果您给出了答案并且他们告诉您您错了,那么您没有理由不能要求他们解释原因。如果 Anatolyg 是正确的,并且这是对您自信心的测试,那么您应该解释为什么您以您的方式回答并问他们同样的问题。
我的意思是你对他们说的第一件事。不能是“为什么 x 更快?” “我不知道;为什么 x 更快?”。显然,在正确回答后,您可以提问。
o
ouah

为了这个问题,我应该补充一下,我记得 C 委员会的 Doug Gwyn 写道,一些没有优化器通过的早期 C 编译器会在汇编中为 while(1) 生成一个测试(与 for(;;) 相比,它没有它)。

我会通过给出这个历史记录来回答面试官,然后说即使我对任何编译器这样做感到非常惊讶,编译器也可以:

如果没有优化器通过,编译器会为 while(1) 和 while(2) 生成一个测试

通过优化器传递,编译器被指示优化(无条件跳转)所有 while(1),因为它们被认为是惯用的。这将使 while(2) 进行测试,因此会在两者之间产生性能差异。

我当然会向面试官补充说,不考虑 while(1)while(2) 相同的构造是低质量优化的标志,因为它们是等效的构造。


P
Peter Mortensen

对此类问题的另一种看法是,看看您是否有勇气告诉您的经理他/她错了!以及你可以多么轻柔地传达它。

我的第一直觉是生成汇编输出,以向经理表明任何体面的编译器都应该处理它,如果它不这样做,您将为其提交下一个补丁 :)


B
Bart Verkoeijen

看到这么多人深入研究这个问题,说明了为什么这很可能是一个测试,看看你想多快地对事物进行微优化。

我的回答是;没关系,我宁愿专注于我们正在解决的业务问题。毕竟,这就是我要付出的代价。

此外,我会选择 while(1) {},因为它更常见,其他队友不需要花时间去弄清楚为什么有人会选择高于 1 的数字。

现在去写一些代码。 ;-)


除非您支付报酬来优化某些实时代码,您只需减少 1 或 2 毫秒即可满足其运行时间需求。当然,这是优化器的工作,有人会说——如果你有一个适合你的架构的优化器。
T
Tom Tanner

如果你担心优化,你应该使用

for (;;)

因为那没有测试。 (愤世嫉俗的模式)


p
pacoverflow

在我看来,这是被伪装成技术问题的行为面试问题之一。有些公司会这样做——他们会问一个技术问题,对于任何称职的程序员来说,这个问题应该很容易回答,但是当面试者给出正确答案时,面试官会告诉他们他们错了。

公司想看看你在这种情况下会如何反应。你是否安静地坐在那里,不要因为自我怀疑或害怕惹恼面试官而强求你的答案是正确的?或者你愿意挑战一个你知道是错误的权威人士吗?他们想看看你是否愿意为自己的信念挺身而出,以及你是否能以委婉和尊重的方式做到这一点。


P
Peter Wooster

当这种胡说八道可能会有所作为时,我曾经对 C 和汇编代码进行编程。当它确实有所作为时,我们在 Assembly 中编写了它。

如果我被问到这个问题,我会重复 Donald Knuth 1974 年关于过早优化的著名名言,如果面试官没有笑并继续前进,我会走路。


鉴于他还说过“在已建立的工程学科中,很容易获得 12% 的改进,从不被认为是边缘的,我相信同样的观点应该在软件工程中占主导地位”,我认为你走是不合理的。
R
Rok Kralj

可能面试官故意提了这么傻的问题,想让你说3点:

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


g
gnasher729

这里有一个问题:如果你真的编写一个程序并测量它的速度,两个循环的速度可能不同!对于一些合理的比较:

unsigned long i = 0;
while (1) { if (++i == 1000000000) break; }

unsigned long i = 0;
while (2) { if (++i == 1000000000) break; }

添加了一些打印时间的代码,一些随机效果(例如循环如何定位在一两个缓存行中)可能会产生影响。一个循环可能完全在一个高速缓存行内,或者在一个高速缓存行的开头,或者它可能跨越两个高速缓存行。结果,无论面试官声称最快的是什么,实际上都可能是最快的——巧合。

最坏的情况:优化编译器不知道循环做了什么,但知道执行第二个循环时产生的值与第一个循环产生的值相同。并为第一个循环生成完整代码,但不是为第二个循环。


“高速缓存线”推理仅适用于具有极小高速缓存的芯片。
这是题外话。关于速度的问题假定“其他条件相同”。
@JimBalter:这不是速度问题,而是与面试官争论的问题。并且进行实际测试可能会证明面试官“正确”的可能性 - 原因与他的论点无关,但纯粹是巧合。如果你试图证明他错了,那会让你陷入尴尬的境地。
@gnasher729 除了所有合乎逻辑的论点,您所说的巧合案例值得一看。但仍然一味地相信这种情况的存在,不仅幼稚而且愚蠢。只要您不解释会发生什么、如何或为什么会发生这种情况,这个答案就毫无用处。
@gnasher729 “这不是速度问题”——我不会与那些使用不诚实修辞技巧的人争论。
P
Peter Mortensen

他们都是平等的——同样的。

根据规范,任何不是 0 的东西都被认为是真的,所以即使没有任何优化,一个好的编译器也不会为 while(1) 或 while(2) 生成任何代码。编译器会为 != 0 生成一个简单的检查。


@djechlin - 因为它们都需要 1 个 cpu 周期。
编译器常量会折叠它,因此它甚至不会在运行时进行评估。
e
ekerner

从人们花在测试、证明和回答这个非常直截了当的问题上的时间和精力来看,我说这两个问题都被问得很慢。

所以要花更多的时间在上面......

“while (2)”很荒谬,因为,

“while (1)”和“while (true)”在历史上被用于创建一个无限循环,该循环期望在循环内部的某个阶段根据肯定会发生的条件调用“break”。

“1”只是为了始终评估为真,因此,说“while (2)”与说“while (1 + 1 == 2)”也将评估为 true 一样愚蠢。

如果你想完全傻,只需使用: -

while (1 + 5 - 2 - (1 * 3) == 0.5 - 4 + ((9 * 2) / 4.0)) {
    if (succeed())
        break;
}

我想认为您的编码员犯了一个不影响代码运行的错字,但如果他故意使用“2”只是为了变得怪异,那么在他把奇怪的东西放在你的代码中之前解雇他!阅读和使用。


您所做的回滚会还原由非常高代表的用户进行的编辑。这些人通常非常了解这些政策,他们是来教你的。我发现您帖子的最后一行对您的帖子没有任何用处。即使我得到了我显然错过的笑话,我也会认为它太健谈,不值得多写一段。
@Palec:感谢您的评论。我发现同一个人编辑了我的许多帖子,主要是为了删除签名。所以我认为他只是一个声誉巨魔,因此他的声誉。在线论坛是否已经耗尽了语言——以及普通的礼节——以至于我们不再在我们的作品上签名?也许我有点老派,但省略你好和再见似乎很粗鲁。我们正在使用应用程序,但我们仍然是人类。
Stack Exchangesalutations, taglines, thanks etc. are not welcomehelp center 中也提到了这一点,特别是在 expected behavior 下。 Stack Exchange 的任何部分,甚至 Stack Overflow,都不是论坛——它们是问答网站。 Editing 是差异之一。此外,评论应仅用于提供对帖子的反馈,而不是用于讨论 (Stack Overflow Chat)。 Q&A 与论坛的不同之处还有很多。在 help centerMeta Stack Overflow 中了解更多信息。
请注意,当您拥有超过 2000 个代表(编辑权限)时,您不会从编辑中获得更多代表。如果对您的帖子应用了您不同意的原因和编辑有疑问,或者您看到某人的不当行为,请在 Meta Stack Overflow 上提问。如果编辑做错事,他们可能会被模组通知(也许他们没有意识到),甚至会受到某种惩罚(如果行为是故意恶意的)。否则,您会得到解释为什么编辑/行为是正确的指针。不必要的回滚会使修订历史变得混乱,并可能使您陷入回滚战。只有在确定编辑不正确时,我才会回滚。
最后是关于签名、问候……:不包括这些手续可能看起来很粗鲁,但它会增加信噪比并且不会丢失任何信息。你可以选择任何你想要的昵称,那就是你的签名。在大多数地方,你也有你的头像。
u
user143522

这取决于编译器。

如果它优化代码,或者如果它对特定指令集使用相同数量的指令将 1 和 2 评估为真,则执行速度将是相同的。

在实际情况下,它总是同样快,但是可以想象一个特定的编译器和一个特定的系统,当它们的评估方式不同时。

我的意思是:这实际上不是与语言(C)相关的问题。


A
Antonin GAVREL

由于希望回答这个问题的人想要最快的循环,我会回答两者都同样编译成相同的汇编代码,如其他答案中所述。不过,您可以使用“循环展开”向面试官建议; do {} while 循环而不是 while 循环。

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

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

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


为什么他会建议使用 do{}while 循环? While(1) (或 while(2)) 做同样的事情。
do while 消除了一个额外的跳跃,因此性能更好。当然,在您知道循环将至少执行一次的情况下
c
chqrlie

显而易见的答案是:正如所发布的,两个片段都会运行一个同样繁忙的无限循环,这使得程序无限慢。

尽管将 C 关键字重新定义为宏在技术上会具有未定义的行为,但这是我能想到的使任一代码片段快速的唯一方法:您可以在 2 个片段上方添加这一行:

#define while(x) sleep(x);

它确实会使 while(1) 的速度是 while(2) 的两倍(或速度的一半)。


h
hdost

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

该代码将循环优化为 cmp eax, 2 当减法发生时,您实际上是在减去 a。 00000000 - 00000010 cmp eax, 2 而不是 b。 00000000 - 00000001 cmp eax, 1

cmp 只设置标志,不设置结果。所以在最低有效位上,我们知道我们是否需要用 b 借用。而对于 a,您必须执行两次减法才能获得借位。


cmp 在这两种情况下都将占用 1 个 cpu 周期。
该代码将不正确。正确的代码会将 2 或 1 加载到寄存器 eax 中,然后将 eax 与 0 进行比较。