提问人:Nikole 提问时间:7/20/2014 最后编辑:CommunityNikole 更新时间:9/16/2022 访问量:97507
while(1) 和 while(2) 哪个更快?
Which is faster: while(1) or while(2)?
问:
这是一位高级经理提出的面试问题。
哪个更快?
while(1) {
// Some code
}
或
while(2) {
//Some code
}
我说两者具有相同的执行速度,因为里面的表达式最终应该计算为 or 。在这种情况下,两者的计算结果都为 并且条件中没有额外的条件指令。因此,两者的执行速度相同,我更喜欢 while (1)。while
true
false
true
while
但面试官自信地说:
“检查你的基础知识。 比 .”
(他不是在测试我的信心)while(1)
while(2)
这是真的吗?
Смотритетакже: “for(;;)” 是否比 “while (TRUE)” 快?如果不是,人们为什么要使用它?
答:
你的解释是正确的。这似乎是一个除了技术知识之外还考验你自信心的问题。
顺便说一句,如果你回答了
这两段代码同样快,因为两者都需要无限的时间才能完成
面试官会说
但每秒可以做更多的迭代;你能解释一下为什么吗?(这是无稽之谈,再次考验你的信心)
while (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
因此,生成的代码是完全相同的,至少在使用优化编译器时是这样。
评论
while
while
while
while (00000001) {}
while (00000000) {}
这两个循环都是无限的,但我们可以看到哪个循环每次迭代需要更多的指令/资源。
使用 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
我不能很好地阅读汇编,但这显然是一个无条件循环。该指令无条件地将程序重置回标签,甚至不将值与 true 进行比较,当然会立即再次这样做,直到程序以某种方式结束。这直接对应于 C/C++ 代码:jmp
.L2
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 指令之间存在一层转换,这一层对性能有重要影响。
- 因此,不能仅通过查看源代码来评估性能。
- 编译器应该足够聪明,可以优化这种微不足道的情况。在绝大多数情况下,程序员不应该浪费时间去思考它们。
评论
-O
jmp
break
while(1)
while(2)
你应该问他是如何得出这个结论的。在任何像样的编译器下,两者编译为相同的 asm 指令。所以,他应该告诉你编译器也开始了。即便如此,你也必须非常了解编译器和平台,才能做出理论上的有根据的猜测。最后,这在实践中并不重要,因为还有其他外部因素,如内存碎片或系统负载,它们对循环的影响比这个细节更大。
评论
这里有一个问题:如果你真的编写一个程序并测量它的速度,两个循环的速度可能会不同!为了进行一些合理的比较:
unsigned long i = 0;
while (1) { if (++i == 1000000000) break; }
unsigned long i = 0;
while (2) { if (++i == 1000000000) break; }
通过添加一些打印时间的代码,一些随机效果(例如循环在一两行缓存中的位置)可能会有所不同。一个循环可能完全在一个缓存行内,或者在缓存行的开头,或者它可能跨越两个缓存行。因此,无论面试官声称什么最快,实际上可能都是最快的——这是巧合。
最坏的情况:优化编译器没有弄清楚循环的作用,但计算出执行第二个循环时生成的值与第一个循环生成的值相同。并为第一个循环生成完整的代码,但不为第二个循环生成完整的代码。
评论
现有的答案显示了由特定编译器为具有特定选项集的特定目标生成的代码,并不能完全回答问题 - 除非该问题是在该特定上下文中提出的(例如,“使用gcc 4.7.2 for x86_64 with default options哪个更快?”)。
就语言定义而言,在抽象机器中计算整数常量,并计算整数常量;在这两种情况下,将结果相等于零进行比较。语言标准完全没有说明这两种结构的相对性能。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.
所以相关的常量表达式是 和 ,两者的计算结果都为 。(这些比较隐含在循环的语义中;它们不作为实际的 C 表达式存在。1 == 0
2 == 0
int
0
while
一个反常的幼稚编译器可能会为这两个构造生成不同的代码。例如,对于第一个,它可以生成一个无条件的无限循环(视为一种特殊情况),对于第二个,它可以生成一个等价于的显式运行时比较。但是我从来没有遇到过一个C编译器会以这种方式运行,我严重怀疑这样的编译器是否存在。1
2 != 0
大多数编译器(我很想说所有生产质量的编译器)都有请求额外优化的选项。在这样的选项下,任何编译器都不太可能为这两种形式生成不同的代码。
如果编译器为这两个构造生成不同的代码,请首先检查不同的代码序列是否实际上具有不同的性能。如果是这样,请尝试使用优化选项(如果可用)再次编译。如果它们仍然不同,请向编译器供应商提交错误报告。从不符合 C 标准的意义上讲,它(不一定)是一个错误,但几乎可以肯定这是一个应该纠正的问题。
一句话:几乎可以肯定具有相同的性能。它们具有完全相同的语义,任何编译器都没有充分的理由不生成相同的代码。while (1)
while(2)
尽管编译器生成比 for 更快的代码是完全合法的,但编译器生成比同一程序中另一个出现的代码更快的代码同样是合法的。while(1)
while(2)
while(1)
while(1)
(你问的那个问题中隐含着另一个问题:你如何与坚持不正确技术观点的面试官打交道。对于 Workplace 网站来说,这可能是一个好问题)。
评论
while
expr != 0
<expr> == 0
while
0
==
!=
while (2)
while (pointer)
while (9.67)
0
1
... == 0
while
x == 0
int
0
1
while
它们都是平等的——一样的。
根据规范,任何不是 0 的东西都被认为是真的,所以即使没有任何优化,一个好的编译器也不会生成任何代码
对于 while(1) 或 while(2)。编译器将对 生成一个简单的检查。!= 0
评论
对这个问题最可能的解释是,面试官认为处理器会逐个检查数字的各个位,直到它达到非零值:
1 = 00000001
2 = 00000010
如果“is zero?”算法从数字的右侧开始,并且必须检查每个位,直到它达到非零位,则循环每次迭代必须检查两倍于循环的位数。while(1) { }
while(2) { }
这需要一个非常错误的计算机工作方式的心智模型,但它确实有自己的内在逻辑。检查的一种方法是询问是否或将同样快,或者是否会更慢。while(-1) { }
while(3) { }
while(32) { }
评论
cmp
-O0
while(1)
当然,我不知道这位经理的真正意图,但我提出了一个完全不同的观点:当雇用一个新成员加入团队时,了解他对冲突情况的反应是有用的。
他们把你逼入冲突。如果这是真的,他们很聪明,问题很好。对于某些行业,例如银行业,将您的问题发布到 Stack Overflow 可能是被拒绝的原因。
但我当然不知道,我只是提出一个选择。
评论
是的,比人类阅读要快得多!如果我在一个不熟悉的代码库中看到,我立即知道作者的意图,我的眼球可以继续到下一行。while(1)
while(2)
while(1)
如果我看到,我可能会停下脚步,试图弄清楚作者为什么不写。作者的手指在键盘上滑了吗?这个代码库的维护者是否使用一种晦涩难懂的注释机制来使循环看起来不同?这是某些损坏的静态分析工具中虚假警告的粗略解决方法吗?或者这是我正在阅读生成的代码的线索?这是由于不明智的查找和替换所有错误导致的错误,还是错误的合并,还是宇宙射线?也许这行代码应该做一些截然不同的事情。也许它应该阅读或.我最好在文件的历史记录中找到作者,然后向他们发送一封“WTF”电子邮件......现在我已经打破了我的心理环境。这可能会占用我几分钟的时间,而这本来只需要几分之一秒!while(2)
while(1)
while(n)
while(w)
while(x2)
while(2)
while(1)
我夸大其词,但只有一点点。代码的可读性非常重要。这在采访中值得一提!
评论
svn-annotate
git blame
1
while(l)
看起来很像......即使是看似无辜的语句也可以隐藏子图块错误。while(1)
while(2)
我能想到为什么会更慢的唯一原因是:while(2)
该代码将循环优化为
cmp eax, 2
当减法发生时,你实际上是在减法
一个。
00000000 - 00000010 cmp eax, 2
而不是
b.
00000000 - 00000001 cmp eax, 1
cmp
只设置标志,不设置结果。因此,在最低有效位上,我们知道是否需要借用 b。而使用 a,您必须在借用之前执行两次减法。
评论
我认为线索可以在“高级经理询问”中找到。这个人显然在成为经理时停止了编程,然后他/她花了几年时间才成为高级经理。从未对编程失去兴趣,但从那时起就再也没有写过一句话。因此,他的参考不是一些答案所提到的“任何像样的编译器”,而是“这个人 20-30 年前与之合作的编译器”。
当时,程序员花费了相当多的时间尝试各种方法,使他们的代码更快、更高效,因为“中央小型计算机”的 CPU 时间非常宝贵。编写编译器的人也是如此。我猜他的公司当时提供的唯一编译器是在“经常遇到的可以优化的语句”的基础上优化的,并且在遇到一段时间(1)时采取了一些捷径,并评估了其他所有内容,包括一段时间(2)。有过这样的经历可以解释他的立场和他对它的信心。
让你被录用的最好方法可能是让高级经理得意忘形,在你顺利地引导他进入下一个面试主题之前,用 2-3 分钟的时间讲授你“编程的美好时光”。(好的时机在这里很重要——太快,你就会打断故事——太慢,你就会被贴上注意力不足的标签)。在面试结束时告诉他,你非常有兴趣了解更多关于这个话题的信息。
对这个问题的另一个看法是,看看你是否有勇气告诉你的经理他/她错了!以及你能多么温柔地传达它。
我的第一个直觉是生成汇编输出,向经理展示任何像样的编译器都应该处理它,如果它不这样做,您将为它提交下一个补丁:)
这取决于编译器。
如果它优化代码,或者如果它对特定指令集使用相同数量的指令将 1 和 2 计算为 true,则执行速度将相同。
在实际情况下,它总是同样快,但是可以想象一个特定的编译器和一个特定的系统,当它被不同的评估时。
我的意思是:这不是一个与语言(C)相关的问题。
看到这么多人深入研究这个问题,恰恰说明了为什么这很可能是一个测试,看看你想以多快的速度对事物进行微优化。
我的答案是;这并不重要,我更愿意专注于我们正在解决的业务问题。毕竟,这就是我要得到的报酬。
此外,我会选择,因为它更常见,其他队友不需要花时间弄清楚为什么有人会选择比 1 更高的数字。while(1) {}
现在去写一些代码。;-)
评论
为了回答这个问题,我应该补充一点,我记得 C 委员会的 Doug Gwyn 写道,一些没有优化器通过的早期 C 编译器会在汇编中为 the 生成测试(相比之下,没有它)。while(1)
for(;;)
我会通过给出这个历史注释来回答面试官,然后说,即使我会非常惊讶任何编译器都这样做,编译器也可以:
- 如果没有优化器通过,编译器会生成一个测试,同时
while(1)
while(2)
- 使用 optimizer pass,编译器被指示进行优化(使用无条件跳转),所有这些都是因为它们被认为是惯用的。这将留下一个测试,因此在两者之间产生性能差异。
while(1)
while(2)
我当然要向面试官补充一点,不考虑和相同的结构是低质量优化的标志,因为这些是等效的结构。while(1)
while(2)
等一会。面试官,他长得像这个人吗?
面试官自己这次面试失败已经够糟糕了, 如果这家公司的其他程序员“通过”了这个测试怎么办?
不。评估语句和应该同样快。我们可以想象糟糕的编译器实现,其中一个可能比另一个更快。但是没有充分的理由说明为什么一个应该比另一个更快。1 == 0
2 == 0
即使有一些晦涩难懂的情况,当声明是正确的,程序员也不应该根据对晦涩(在这种情况下是令人毛骨悚然的)琐事的了解来评估。不要担心这次采访,这里最好的举动就是走开。
免责声明:这不是原创的 Dilbert 卡通片。这只是一个混搭。
评论
cmp
cmp
cmp
while(1)
while(200)
从人们花费的时间和精力来测试、证明和回答这个非常直接的问题来看,我想说,通过提出这个问题,两者都变得非常缓慢。
所以花更多的时间在上面......
while (2)
是荒谬的,因为,
while (1)
,历史上用于制造无限循环,该无限循环期望在循环内的某个阶段根据肯定会发生的条件被调用。while (true)
break
只是在那里总是评估为真,因此,说和说它也会评估为真一样愚蠢。1
while (2)
while (1 + 1 == 2)
如果你想完全傻,只需使用:-
while (1 + 5 - 2 - (1 * 3) == 0.5 - 4 + ((9 * 2) / 4.0)) {
if (succeed())
break;
}
我认为面试官打错了一个错字,这并没有影响代码的运行,但是如果他故意使用“只是为了奇怪”,那么在他把奇怪的陈述全部放在你的代码中之前,就解雇他,使其难以阅读和使用。2
评论
我曾经对 C 和汇编代码进行编程,当时这种废话可能会有所作为。当它确实有所作为时,我们将其编写在 Assembly 中。
如果有人问我这个问题,我会重复唐纳德·克努斯(Donald Knuth)1974年关于过早优化的名言,如果面试官不笑并继续前进,我就会走开。
评论
在我看来,这是被掩盖为技术问题的行为面试问题之一。有些公司会这样做——他们会问一个技术问题,对于任何有能力的程序员来说都应该很容易回答,但是当受访者给出正确答案时,面试官会告诉他们他们错了。
该公司想看看你在这种情况下会如何反应。你是否安静地坐在那里,不强迫你的答案是正确的,因为自我怀疑或害怕惹恼面试官?或者你愿意挑战一个你知道是错的当权者?他们想看看你是否愿意坚持自己的信念,以及你是否能以委婉和尊重的方式做到这一点。
也许面试官故意提出这么愚蠢的问题,想让你提出 3 点:
- 基本推理。两个循环都是无限的,很难谈论性能。
- 了解优化级别。他想听听你的意见,如果你让编译器为你做任何优化,它会优化条件,特别是如果块不是空的。
- 了解微处理器架构。大多数架构都有一个特殊的 CPU 指令,用于与 0 进行比较(虽然不一定更快)。
如果您担心优化,则应使用
for (;;)
因为那没有测试。(愤世嫉俗模式)
由于想要回答这个问题的人想要最快的循环,我会回答说,正如其他答案中所述,两者都同样编译成相同的汇编代码。尽管如此,你还是可以使用“循环展开”向面试官提出建议;一个 do {} while 循环而不是 while 循环。
谨慎:您需要确保循环至少始终运行一次。
循环内部应有断裂条件。
同样对于这种循环,我个人更喜欢使用 do {} while(42),因为除了 0 之外的任何整数都可以完成这项工作。
评论
显而易见的答案是:正如发布的那样,两个片段将运行一个同样繁忙的无限循环,这使得程序无限慢。
尽管从技术上讲,将 C 关键字重新定义为宏会具有未定义的行为,但这是我能想到的唯一方法,可以使任何一个代码片段都变得快速:您可以在 2 个片段上方添加以下行:
#define while(x) sleep(x);
它确实会比 .while(1)
while(2)
评论
0x100000f90: jmp 0x100000f90