Nik*_*le 582 c performance while-loop
这是一位高级经理提出的面试问题.
哪个更快?
while(1) {
// Some code
}
Run Code Online (Sandbox Code Playgroud)
要么
while(2) {
//Some code
}
Run Code Online (Sandbox Code Playgroud)
我说两者都有相同的执行速度,因为里面的表达式while应该最终评估为true或false.在这种情况下,两者都评估,true并且条件内没有额外的条件指令while.因此,两者都具有相同的执行速度,而我更喜欢(1).
但采访者自信地说:"检查你的基础知识.while(1)比快while(2)." (他没有测试我的信心)
这是真的?
App*_*ish 676
两个循环都是无限的,但是我们可以看到哪个循环每次迭代需要更多的指令/资源.
使用gcc,我将以下两个程序编译为不同优化级别的程序集:
int main(void) {
while(1) {}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
int main(void) {
while(2) {}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
即使没有优化(-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"
Run Code Online (Sandbox Code Playgroud)
用-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"
Run Code Online (Sandbox Code Playgroud)
用-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"
Run Code Online (Sandbox Code Playgroud)
实际上,为循环生成的程序集对于每个优化级别都是相同的:
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
Run Code Online (Sandbox Code Playgroud)
重要的是:
.L2:
jmp .L2
Run Code Online (Sandbox Code Playgroud)
我不能很好地阅读汇编,但这显然是一个无条件的循环.该jmp指令无条件地将程序重置回.L2标签,甚至没有将值与true进行比较,当然也会立即再次执行,直到程序以某种方式结束.这直接对应于C/C++代码:
L2:
goto L2;
Run Code Online (Sandbox Code Playgroud)
编辑:
有趣的是,即使没有优化,以下循环都会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) {}
Run Code Online (Sandbox Code Playgroud)
令我惊讶的是:
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
Run Code Online (Sandbox Code Playgroud)
用户定义的函数使事情变得更有趣:
int x(void) {
return 1;
}
while(x()) {}
Run Code Online (Sandbox Code Playgroud)
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
Run Code Online (Sandbox Code Playgroud)
在-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"
Run Code Online (Sandbox Code Playgroud)
第二个例子(返回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"
Run Code Online (Sandbox Code Playgroud)
但是,-O1除此之外,它们都生成与前面示例相同的程序集(无条件地jmp返回到前面的标签).
在GCC下,不同的循环被编译为相同的程序集.编译器会评估常量值,并且不会执行任何实际比较.
这个故事的寓意是:
Chr*_*ter 277
是的,while(1)比快得多while(2),对于人类阅读!如果我while(1)在一个不熟悉的代码库中看到,我立即知道作者的意图,我的眼球可以继续下一行.
如果我看到while(2),我可能会停下来试图找出作者没写的原因while(1).作者的手指在键盘上滑动了吗?这个代码库的维护者是否使用while(n)了一个模糊的注释机制来使循环看起来不同?对于某些破碎的静态分析工具中的虚假警告,这是一个粗略的解决方法吗?或者这是我正在阅读生成代码的线索吗?这是一个由于不明智的查找和替换全部,或者是一个糟糕的合并或一个宇宙射线导致的错误?也许这行代码应该做一些截然不同的事情.也许应该阅读while(w)或while(x2).我最好在文件的历史中找到作者并向他们发送"WTF"电子邮件......现在我已经打破了我的心理背景.将while(2)可能会消耗我的时间几分钟,当while(1)将采取第二的分数!
我夸张了,但只是一点点.代码可读性非常重要.这在采访中值得一提!
Kei*_*son 151
显示特定编译器为具有特定选项集的特定目标生成的代码的现有答案不能完全回答问题 - 除非在该特定上下文中询问了问题("使用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;
}
Run Code Online (Sandbox Code Playgroud)
需要诊断; 懒惰的编译器没有选择将评估推迟2+2到执行时间.由于编译器必须能够在编译时计算常量表达式,因此即使不需要,也没有充分的理由不利用该功能.
C标准(N1570 6.8.5p4)说明了这一点
迭代语句会导致一个称为循环体的语句重复执行,直到控制表达式比较等于0.
所以相关的常量表达式是1 == 0和2 == 0,它们都评估int值0.(这些比较隐含在while循环的语义中;它们不作为实际的C表达式存在.)
一个反常天真的编译器可以为这两个结构生成不同的代码.例如,对于第一个,它可以生成无条件无限循环(1视为特殊情况),对于第二个,它可以生成等效于的显式运行时比较2 != 0.但我从未遇到过实际上会以这种方式运行的C编译器,我非常怀疑这样的编译器是否存在.
大多数编译器(我很想说所有生产质量编译器)都可以选择进行额外的优化.在这样的选项下,任何编译器都不太可能为这两种形式生成不同的代码.
如果编译器为这两个构造生成不同的代码,请首先检查不同的代码序列是否实际上具有不同的性能.如果是,请尝试使用优化选项再次编译(如果可用).如果它们仍然不同,请向编译器供应商提交错误报告.它不是(必然)一个不符合C标准的错误,但它几乎肯定是一个应该纠正的问题.
底线:while (1)和while(2) 几乎肯定有同样的表现.它们具有完全相同的语义,并且没有充分的理由让任何编译器不生成相同的代码.
尽管它是完全合法的编译器生成速度更快的代码进行while(1)比对while(2),这是同样的法律对编译器生成速度更快的代码while(1)比另一个发生while(1)在相同的程序.
(你问的那个问题隐含着另一个问题:你如何处理一个坚持不正确的技术要点的面试官.这可能是Workplace网站的一个好问题).
Sto*_*ica 137
等一下.面试官,他看起来像这个家伙?
面试官本人未能通过这次面试,这很糟糕,如果这家公司的其他程序员"通过"了这个测试呢?
不.评估陈述1 == 0并且2 == 0 应该同样快.我们可以想象糟糕的编译器实现,其中一个可能比另一个更快.但是没有充分的理由说为什么一个人应该比另一个人更快.
即使有一些模糊的情况,当声称是真的,程序员也不应该根据模糊(在这种情况下,令人毛骨悚然)的琐事的知识进行评估.不要担心这次采访,这里最好的举动就是走开.
免责声明:这不是原始的Dilbert漫画.这只是一个混搭.
ana*_*lyg 79
你的解释是正确的.除了技术知识之外,这似乎是一个测试你的自信心的问题.
顺便问一下,如果你回答
这两段代码同样快,因为两者都需要无限的时间才能完成
面试官会说
但是
while (1)每秒可以进行更多的迭代; 你能解释一下原因吗?(这是废话;再次测试你的信心)
所以通过像你一样回答,你节省了一些时间,否则你会浪费在讨论这个糟糕的问题上.
以下是我的系统(MS Visual Studio 2012)上的编译器生成的示例代码,关闭了优化:
yyy:
xor eax, eax
cmp eax, 1 (or 2, depending on your code)
je xxx
jmp yyy
xxx:
...
Run Code Online (Sandbox Code Playgroud)
启用优化后:
xxx:
jmp xxx
Run Code Online (Sandbox Code Playgroud)
所以生成的代码完全相同,至少使用优化编译器.
Rya*_*ugh 60
这个问题最可能的解释是,面试官认为处理器一个接一个地检查数字的各个位,直到它达到非零值:
1 = 00000001
2 = 00000010
Run Code Online (Sandbox Code Playgroud)
如果"为零?" 算法从数字的右侧开始,并且必须检查每个位直到它达到非零位,while(1) { }循环必须检查每次迭代的两倍于while(2) { }循环.
这需要一个非常错误的计算机工作方式的心智模型,但它确实有自己的内部逻辑.检查的一种方法是询问是否while(-1) { }或while(3) { }同样快,或者是否while(32) { }会 更慢.
Tõn*_*uel 32
当然,我不知道这位经理的真实意图,但我提出了一个完全不同的观点:当雇用一名新成员加入团队时,了解他如何应对冲突局势是有用的.
他们让你陷入冲突.如果这是真的,他们很聪明,问题很好.对于某些行业,例如银行业务,将您的问题发布到Stack Overflow可能是拒绝的原因.
但我当然不知道,我只提出一个选择.
Old*_*ank 25
我认为线索可以在"高级经理问"中找到.这个人在成为经理时显然已停止编程,然后他/她花了几年时间成为高级经理.从来没有对编程感兴趣,但从那时起从未写过一条线.所以他的参考文献不是"任何体面的编译器",正如一些答案所提到的那样,而是"这个人在20 - 30年前工作过的编译器".
那时,程序员花费了相当大的时间来尝试各种方法来使代码更快更有效,因为"中央小型机"的CPU时间非常有价值.就像编写编译器的人一样.我猜他的公司当时提供的唯一编译器是根据"经常遇到的可以优化的语句"进行优化的,并在遇到一段时间(1)时采取了一些快捷方式并评估了所有内容否则,包括一段时间(2).有过这样的经历可以解释他的立场和对此的信心.
让你受雇的最佳方法可能就是让高级经理在你顺利领导他接下来的面试主题之前,在"编程的美好时光"上讲课2-3分钟.(好时机在这里很重要 - 太快了,你打断了故事 - 太慢了,你被贴上了一个焦点不足的人).在面试结束时告诉他你对这个话题有更多的了解.
Val*_*adu 19
你应该问他他是如何得出这个结论的.在任何体面的编译器下,两个编译为相同的asm指令.所以,他应该告诉你编译器也要开始.即使这样,你也必须非常了解编译器和平台,甚至做出理论上有根据的猜测.最后,它在实践中并不重要,因为还有其他外部因素,如内存碎片或系统负载,这将影响循环而不是这个细节.
oua*_*uah 18
为了这个问题,我应该补充一下,我记得来自C委员会的Doug Gwyn写道,一些没有优化器传递的早期C编译器会在汇编中生成一个测试while(1)(比较for(;;)没有它的).
我会通过给出这个历史记录回答面试官,然后说即使我对任何编译器都这样做感到非常惊讶,编译器也可以:
while(1)和while(2)while(1)因为它们被认为是惯用的.这将留下while(2)测试,因此在两者之间产生性能差异.我当然会向面试官添加不考虑while(1)和while(2)相同的构造是低质量优化的标志,因为这些是等效的构造.
小智 9
对这样一个问题的另一个看法就是看你是否有勇气告诉你的经理他/她错了!你可以多么轻柔地沟通它.
我的第一直觉是生成程序集输出以向管理员显示任何体面的编译器应该处理它,如果它没有这样做,你将为它提交下一个补丁:)
为了看到这么多人深入研究这个问题,请说明为什么这很可能是一个测试,看看你想要多快地微观优化事物.
我的回答是; 这并不重要,我更关注我们正在解决的业务问题.毕竟,这就是我要付出的代价.
此外,我会选择,while(1) {}因为它更常见,而其他队友则不需要花时间弄清楚为什么有人会选择高于1的数字.
现在去写一些代码.;-)
在我看来,这是掩饰为技术问题的行为面试问题之一。有些公司会这样做——他们会问一个任何有能力的程序员都应该很容易回答的技术问题,但是当受访者给出正确答案时,面试官会告诉他们他们错了。
公司想看看你在这种情况下会如何反应。由于自我怀疑或害怕让面试官不高兴,你是否安静地坐在那里,不强调你的答案是正确的?或者你愿意挑战一个你知道是错误的权威人士吗?他们想看看您是否愿意坚持自己的信念,以及您是否可以以委婉和尊重的方式做到这一点。
也许面试官故意问这么蠢的问题,就是想让你说出3点:
如果您担心优化,则应使用
for (;;)
Run Code Online (Sandbox Code Playgroud)
因为那没有测试。(讽刺模式)
| 归档时间: |
|
| 查看次数: |
87062 次 |
| 最近记录: |