这是一个高级经理问的面试问题。
哪个更快?
1 2 3
| while(1) {
// Some code
} |
或
1 2 3
| while(2) {
//Some code
} |
我说这两个都有相同的执行速度,因为while中的表达式最终应该对true或false进行评估。在这种情况下,都对true进行评估,并且在while条件中没有额外的条件指令。因此,两种方法的执行速度相同,我更喜欢while(1)。
但采访者自信地说:"检查你的基础知识。while(1)比while(2)快。(他没有考验我的信心)
这是真的吗?
另请参见:"for(;;)"是否比"while(true)"更快?如果没有,人们为什么要使用它?
- 一个半熟的编译器将把这两种形式优化为零。
- 在优化构建中每隔一段时间(n),n!=0或for(;;)将转换为以标签开头、以goto结尾的程序集无休止循环。完全相同的代码,相同的性能。
- 不足为奇,股票优化为这两个片段带来了0x100000f90: jmp 0x100000f90(地址明显不同)。面试官可能在一个注册测试中回避了,而不是简单的标记跳转。这个问题和他们的假设都是站不住脚的。
- 面试官提出的这个问题与dilbert.com/strips/comic/1995-11-17有着相同的支持——你会遇到一个真正相信自己所说的话的人,而不管他们的陈述中有多愚蠢。简单地从以下几个方面进行选择:深呼吸,发誓,大笑,哭泣,以上的一些组合。
- 难怪面试官是"高级经理"。公司可能负担不起有这样一个工程师。
- 什么样的高级经理会问这样的问题?
- 我希望你能得到一份工作!但我也希望你不必在那个特别的"高级经理"手下工作
- 当他们问你这个问题的时候,它是在一个代码块中,像你拥有它的方式那样分开,还是和你说话?
- @船长长颈鹿这个问题不是重复的。我不知道如何从问题中删除可能的重复标志
- @酋长笔这是在讨论中问的问题。没有代码。
- 167名"高级管理人员"否决了第一条评论。:)
- 正确的答案是"即使高级经理说速度更快,即使他认为不是",否则就站起来离开,因为任何项目经理都会相信高级经理,如果他不懂编程,就不会相信你。
- @Mike W:我们可以想一想编译器应该怎么做:转换成一条HALT语句,或者考虑无限时间后循环退出并优化消除无限延迟?
- 有人能告诉我如何从问题描述中删除重复的问题标志吗
- 也许这是一个考验,看看你是否只是屈服于权威。面试也很糟糕,只是方式不同。
- 而(真);btw,(int)真是-1;
- 请给你的面试官引言。尽管事实证明两者是相同的。但在得出结论之前,我们应该了解他,听听他对故事的看法。;();)
- 显然,编译器可以完全优化(参见这个C++问题或这个讨论-搜索"C1X"来引用标准),一些编译器实际上是这样做的(后一个链接提到哪一个)。
- @这个:那么你如何解释-1比0慢?
- 面试官显然犯了一个错误。
- 这个问题不要求在运行时更快地迭代,也不要求在更短的时间内编译。它只是问哪个更快。因此,无论哪一个是第一个,显然是两个选择中更快的;因此,尽管(1)必须更快。杜赫
- 为什么三天内会有这么多的意见、投票和赞成票?!
- @罗布施穆克接着说还有更多的"老白痴"(呃..管理者)"在这个世界上比人们想象的要多。
- @Devnull,真是疯了,以为这已经比4年前标记的"副本"多了20万个视图了?!这个怎么引起这么多关注?!也许是"红"字?
- @可能是罗布施穆克。我想不出更多愚蠢的问题。向那些反对这篇文章的人致敬!
- 我想知道公司的名称,这样我就可以避免在那里工作。
- 我很想看到面试官在StackOverflow上表明自己的身份,并为这个答案做出贡献。他/她从不用它来查任何东西,哦,来吧。
- 如果他/她"不是在考验你的信心",他/她就是在考验你是否愿意忍受无稽之谈、空洞和毫无根据的自信和傲慢。或者,测试一下你是否愿意在需要的时候质疑权威。不管怎样,都要拼命跑!!!!!
- 也许他们在找你澄清他们是否在寻找编译时间和执行时间。我不确定它们的编译时间是否相同,因为我可以看到一些编译器有"while(1)"和"while(true)"的早期版本,或者将其传递给更通用的const表达式计算器。
- 正如@mooseboys提到的。我的第一个想法是他可能在谈论构建时间而不是运行时间。如果能以某种方式调查这件事就好了。
- 这个问题只是为了吸引选票。
- @尼科尔什么公司?
- @pts这个问题可能只是为了吸引选票,但它正是关于堆栈溢出的主题。
- 当公司面试你时,你也在面试公司。如果他们显然是由白痴管理的,你希望他们出现在你的简历上吗?
- 迈克:不,它会使回路保持完整。编译器不应该改变程序的含义。如果程序不终止,它应该保持这种方式。
- 是的,值得注意的是,165个假人否决了这个错误的评论。
- 如果他愿意证明他是对的,我会给他100美元。
- 我认为写while(1)比写while(2)快。碰巧,我刚从亚马逊订购了一支新的机械铅笔。它一到,我就运行一些基准。
- 考官完全错了,速度最快的无疑是while (0);-)
- 你得到这份工作了吗?
- 这个问题只是在quora.com/programming-which-is-faster-while-1-or-while-2上被抄袭。
这两个循环都是无限的,但是我们可以看到每次迭代哪个循环需要更多的指令/资源。
使用gcc,我编译了以下两个程序,以在不同的优化级别进行组装:
1 2 3 4
| int main(void) {
while(1) {}
return 0;
} |
1 2 3 4
| int main(void) {
while(2) {}
return 0;
} |
即使没有优化(-O0),生成的程序集对于两个程序都是相同的。因此,两个回路之间没有速度差。
以下是生成的程序集(使用带优化标志的gcc main.c -S -masm=intel):
使用-O0时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| .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时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| .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时(输出相同):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .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" |
实际上,为循环生成的程序集对于每一级优化都是相同的:
1 2 3 4
| .L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1" |
重要的是:
我不能很好地阅读汇编,但这显然是一个无条件循环。jmp指令无条件地将程序重置回.L2标签,而不必将值与真值进行比较,当然,在程序以某种方式结束之前,它会立即再次这样做。这直接对应于C/C++代码:
编辑:
有趣的是,即使没有优化,以下循环在程序集中都产生了完全相同的输出(无条件的jmp)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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) {} |
甚至令我惊讶的是:
1 2 3 4 5
| #include<math.h>
while(sqrt(7)) {}
while(hypot (3,4)) {} |
用户定义的函数会让事情变得更有趣:
1 2 3 4 5
| int x(void) {
return 1;
}
while(x()) {} |
< BR>
1 2 3 4 5 6 7
| #include<math.h>
double x (void) {
return sqrt(7);
}
while(x ()) {} |
在-O0中,这两个示例实际上称为x并对每个迭代进行比较。
第一个示例(返回1):
1 2 3 4 5 6 7 8 9 10
| .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)):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .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回到前面的标签)。
DR
在gcc下,不同的循环被编译成相同的程序集。编译器计算常量值,不需要执行任何实际比较。
故事的寓意是:
- C++源代码和CPU指令之间存在一层翻译,这一层对性能有重要的影响。
- 因此,不能只通过查看源代码来评估性能。
- 编译器应该足够聪明,以优化这些琐碎的情况。在绝大多数情况下,程序员不应该浪费时间去思考它们。
- 也许面试官没有使用GCC
- @mattmcnab这是一个很好的观点;不幸的是,我的系统上没有安装任何其他编译器。如果有人能测试Clang、MSV或其他东西,我会很好奇看到结果。
- @Matt McNabb这是一个很好的观点,但是如果面试官依赖于编译器特定的优化,那么他们需要在他们的问题中非常明确地说明这一点,并且他们需要接受"没有区别"的答案作为对某些人(大多数人)的正确回答。编译器。
- 为了消除任何疑问,我在Clang 3.4.2中对此进行了测试,并且两个回路在每个-O级别上都产生相同的组件。
- 我一点也不觉得奇怪,因为您在循环的条件部分中放置的所有内容都是编译时常量。因此,我怀疑编译器会看到循环始终是对的或错的,或者简单地从jmp返回到开始,或者完全删除循环。
- @地毯吸烟者:这消除了对你使用它的特殊配置中3.4.2撞击声行为的任何怀疑。
- 这实际上是面试的一个很好的策略:你用最长的回答让面试官精疲力竭,当你完成面试后,就没有空间再提问题了。
- 有趣的答案。我想知道如果你把编译器提示给inline你的用户函数会发生什么…
- 瓦勒克哈特-你有一颗饱满的心!为了检查它:),还有太多的空闲时间;)
- 对于用户定义的函数,这个答案提供了一个解释。
- fyi、.seh_endproc和.ident "GCC: (tdm64-2) 4.8.1"不是循环的汇编语言的一部分。第一个标记当前函数的结束,第二个标记用创建它的编译器的标识来注释对象文件。(通常在循环和.seh_endproc之间会有一个"函数尾声",清除堆栈帧并发出ret指令,但编译器已经意识到控制永远不会通过无限循环,因此省略了它,因为这是不必要的。)
- 你打过电话吗?
- 现在,我用的是42个火环。
- 我认为@valekhalfheart是一个采访者,他说while(1)只是让@nikole问这个问题,然后提供一个很好的答案,速度更快。
- +1尤其是对于"编译器可以将这些循环优化为相同的,因此应该这样做":这是一条黄金法则。
- 你已经证明了相关性,但声称是因果关系。这可能是因为GCC优化了这种方式,因为它看到循环中没有发生任何事情。
- @Hippietrail我不知道循环的内容(或缺少内容)如何可能影响这些优化(除了任何break语句的可能性),但我还是测试了它,没有,即使循环中有代码,对于while(1)和while(2)来说,跳转也是绝对和无条件的。如果你真的很担心的话,你可以自己去测试其他人。
- 这里的基本前提似乎是:所有编译器,至少,将选择cmp eax, 0作为首选算法(或本地机器等价于"结果等于零"),高级分析(优化)几乎肯定会产生无条件跳转。编译器做任何其他事情的可能性似乎是非常荒谬的,因为这是实现测试的最简单方法。
- 这个示例程序太简单了,请阅读:blog.llvm.org/2011/05/&hellip;,编译器可以做任何事情,因为循环不会在此终止。它们可以完全跳过它,或者繁殖蝴蝶。
- @奥多,那只是关于未定义的行为;它没有提到无限循环。
- gcc -O0实际上并不意味着没有优化。这意味着只需要最少的时间来生成Braindead可调试代码,还需要完成GCC内部使用的所有中间表示。制作比-O0更糟糕的代码,这更像是源代码的直译,这不是任何人都实现过的功能。-O0的目标是快速编译,使代码在调试器中始终可以一行一行地执行。-Og是类似的:优化调试。
- "检查你的基础知识。而(1)比(2)快。"
- 如果面试官没有使用gcc,他们应该问我的编译器哪个版本更快。
是的,对于一个人来说,while(1)比while(2)快得多!如果我在一个不熟悉的代码库中看到while(1),我马上就知道作者的意图,我的眼球可以继续往下一行。
如果我看到while(2),我可能会停下脚步,试图弄明白作者为什么不写while(1)。作者的手指在键盘上滑了吗?这个代码库的维护者是否使用while(n)作为一个模糊的注释机制,使循环看起来不同?在一些损坏的静态分析工具中,这是一个错误警告的粗略解决方法吗?或者这是我正在读取生成代码的线索?它是一个错误的发现和替换所有的,还是一个错误的合并,还是宇宙射线?也许这行代码应该做一些显著不同的事情。也许应该读到while(w)或while(x2)。我最好在文件的历史记录中找到作者,然后给他们发一封"wtf"电子邮件…现在我已经打破了我的精神环境。埃多克斯一号〔6〕可能会占用我几分钟的时间,而此时,埃多克斯一号〔5〕可能只花了不到一秒钟的时间!
我有点夸张,但只是有点夸张。代码可读性非常重要。这在面试中值得一提!
- …或者可能是OP的面试官写的时间循环。
- 绝对不夸张。
- 我的想法是准确的,也就是为什么当(1)速度更快的时候,LOL虽然我认为这只是一个老例子,一个经理试图说服自己他知道编码,但他不知道,我们都看到了,每个人都想成为绝地武士。
- 回答得很好。可能正是面试官希望听到的!"检查你的基础知识。而(1)比(2)快。"
- 我认为这应该是一个公认的答案,因为它明显是在开玩笑,所以被低估了。
- 当然,这一点也不夸张。当然,这里将使用svn-annotate 或git blame或其他(或其他)文件,并且通常需要几分钟来加载一个文件指责历史记录。然后才最终决定"啊,我明白了,作者刚高中毕业就写了这句话",才输了10分钟…
- 赞成"宇宙射线":)
- 投票否决,因为我厌倦了惯例。开发人员有这个小小的机会来写他喜欢的数字,然后有人无聊地来抱怨为什么不是1。
- 我花了很多时间阅读这篇文章(这不是一个有效的答案,但得到了162票,比基思·汤普森的完美答案还要多),然后我会花更多的时间看while (2)。代码可读性我的左脸颊…为此,请写下while (true)。(我为博格丹感到难过。)
- 因言辞而被否决。读取while(1)的速度与while(2)的速度完全相同。
- while(l)看起来很像while(1)…即使是看似无辜的陈述也能隐藏字幕错误。
- 当我在代码中看到while(2)时,我经历了与一分钟前这个答案中描述的完全相同的心理过程(下一步是考虑"这个代码库的维护者使用(n)作为一个模糊的注释机制使循环看起来不同吗?")所以不,你不夸张!
- @一个不错的测试编辑器会使用不同的颜色来表示数字和变量名。甚至VIM也有语法突出显示。
- @Cacahuetefrito:也许吧,但不是所有的编辑都这么做。例如,默认情况下,emacs对数字和变量不使用不同的颜色。qemacs也没有,我个人的着色选择对变量和数字使用稍微不同的绿色阴影,这并不能解决模糊性。错误消息也不总是彩色的。使用明确的名称可以解决所有情况下的问题。
- @chqrlie emacs默认值不合适。不管怎样,虽然(真的)比他们两个都快:)
- @Cacahuetefrito:我比较喜欢for(;;),不需要。
显示特定编译器为具有特定选项集的特定目标生成的代码的现有答案并不能完全回答该问题,除非在特定上下文中提出该问题("使用带有默认选项的gcc 4.7.2更快吗?"例如。
就语言定义而言,在抽象机中,while (1)计算整数常数1,while (2)计算整数常数2;在这两种情况下,结果都比较等于零。语言标准完全没有说明这两个构造的相对性能。
我可以想象,一个非常幼稚的编译器可能会为这两种形式生成不同的机器代码,至少在编译时不会请求优化。
另一方面,当某些常量表达式出现在需要常量表达式的上下文中时,C编译器必须在编译时对它们进行计算。例如,这:
1 2 3 4 5
| int n = 4;
switch (n) {
case 2+2: break;
case 4: break;
} |
需要诊断;懒惰的编译器不能选择将2+2的计算推迟到执行时间。因为编译器必须能够在编译时计算常量表达式,所以即使不需要它,也没有充分的理由不利用这个功能。
C标准(N1570 6.8.5P4)规定
An iteration statement causes a statement called the loop body to be
executed repeatedly until the controlling expression compares equal to
0.
因此,相关的常量表达式是1 == 0和2 == 0,这两个表达式的值都是int值0。(这些比较隐含在while循环的语义中;它们不作为实际的C表达式存在。)
一个非常幼稚的编译器可以为这两个构造生成不同的代码。例如,首先它可以生成一个无条件的无限循环(将1视为特殊情况),其次它可以生成一个与2 != 0等效的显式运行时比较。但我从来没有遇到过一个真正的C编译器,它会以这种方式运行,我严重怀疑这样的编译器是否存在。
大多数编译器(我想说的是所有生产质量的编译器)都有请求额外优化的选项。在这种情况下,任何编译器都不太可能为这两种形式生成不同的代码。
如果编译器为这两个构造生成不同的代码,首先检查不同的代码序列是否具有不同的性能。如果有,请使用优化选项(如果可用)再次尝试编译。如果它们仍然不同,请向编译器供应商提交一份错误报告。从未能符合C标准的意义上来说,这不一定是一个bug,但几乎可以肯定这是一个应该纠正的问题。
底线:while (1)和while(2)几乎肯定具有相同的性能。它们的语义完全相同,任何编译器都没有理由不生成相同的代码。
虽然编译器为while(1)生成的代码比为while(2)生成的代码更快是完全合法的,但编译器为while(1)生成的代码比同一程序中出现的while(1)生成的代码更快也是同样合法的。
(你问的另一个问题是:你如何处理一个坚持错误技术观点的面试官。对于工作场所站点来说,这可能是一个很好的问题)。
- "在这种情况下,相关(隐式)常量表达式是1!= 0和2!=0,两者的值均为int值1"…这太复杂了,而且不准确。该标准简单地说,while的控制表达式必须是标量类型,循环体将被重复,直到表达式比较等于0。它并没有说有一个隐含的expr != 0被评估…这就需要这个结果——0或1——依次与0,ad无穷大进行比较。不,将表达式与0进行比较,但该比较不会产生值。我赞成。
- @吉米巴特:我明白你的意思,我会更新我的答案来解决它。不过,我的意思是,标准的措辞是"…在控制表达式比较等于0之前,"意味着对 == 0进行评估;这就是c中"比较等于0"的含义。这种比较是while循环语义的一部分。无论是在标准中还是在我的回答中,都没有暗示结果需要再次与0进行比较。(我本应该写==,而不是!=),但我的回答部分不清楚,我会更新。
- "comparises equal to 0"在c中的意思是——但该语言是标准语言,而不是c…实现进行比较,但不生成C语言比较。您编写"这两个函数的计算结果都是int值1"——但从未发生过这样的计算。如果您查看由最简单、最不优化的编译器为while (2)、while (pointer)、while (9.67)生成的代码,您将看不到生成0或1的任何代码。"我应该写==而不是!="——不,那没有任何意义。
- @吉米巴特:嗯,我不是说"比较等于0"意味着存在一个... == 0c表达。我的观点是,标准对while循环的描述所要求的"比较等于0",而显式x == 0表达式在逻辑上意味着相同的操作。我认为,一个非常幼稚的C编译器可能会生成一个代码,它为任何while循环生成一个int值的0或1,尽管我不认为任何实际的编译器是如此幼稚。
- "我不是要建议"…但你确实做到了,当你写"我认为一个幼稚的C编译器可能会生成代码,为任何while循环生成一个int值0或1"。请看Anatolyg答案中实际的幼稚编译器生成的代码:控制表达式和零之间的显式比较(在目标机器中,而不是在C中)。这就是标准所要求的,而不是对一些产生0或1的C表达式的计算。如果你还是不明白,进一步的讨论也无济于事,所以我就到此为止了。
- 语言定义是否允许编译器对这两个表达式执行任何它想要的操作?我个人鄙视使无副作用无限循环ub的想法(我认为更好的定义是说编译器在其结果影响程序产生的副作用之前不需要执行任何计算,并且执行时间即使是无限的也不被视为副作用)。要定义行为,代码至少应该定义一个全局不稳定无符号变量,并在循环中递增它。
- @超级卫星:没有未定义的行为。C11添加了一个新规则(N1570 6.8.5P6),导致一些无限循环具有未定义的行为,但仅当控制表达式不是常量表达式时才适用。int main(void){while (1){}}的行为定义明确,没有副作用,也没有终止。
- @基思汤普森:这条规则是否会使int shouldHang; while(1) if (!shouldHang) break;不同于while(shouldHang);〔认识到在这两种情况下,如果shouldHang一旦被观察到为真,编译器就没有义务注意它的值是否被外部修改〕。此外,是否有任何东西可以澄清"假设循环终止"是否等同于"假设循环因C标准允许的某种原因终止"?你对我提出的规则/澄清有何看法?
- @Supercat:就个人而言,我认为终止和不终止应该被视为一种副作用。显然,委员会(可能受到编译器供应商的影响)认为,打破这一假设所提供的优化机会是值得的。我不喜欢"可能被实现假定为终止"的措辞;该标准应该描述程序的行为是如何被要求(或不被要求)的,并表示,就编译器的内部"假设"而言,这是马虎的。如果由我决定,我不会"改进"这条规则,我会用核武器。
- @基思汤普森:允许与其他代码相关的未排序代码可以与该代码并行运行,并允许编译器将无副作用循环视为与既不计算循环使用的值(因此必须先于循环使用的值)也不使用循环中计算的值(和m)相关的未排序代码。因此必须遵循它)。如果允许编译器考虑未排序的循环(即使在编译器无法证明循环终止的情况下),则为后一个允许值。请注意,正如我提议的那样,…
- …编译器将需要产生一个与让编译器启动一个新的并行执行单元一致的结果,每次主线程到达该循环的开始时,都在无休止的循环中运行代码。
- 注意:这已经是一个工作场所问题:workplace.stackexchange.com/questions/4314/&hellip;
- @KeithThompson:这是一个大家在谈论终止和优化可能性时都应该记住的阅读:blog.llvm.org/2011/05/&hellip;和:blog.regehr.org/archives/140
- 我喜欢你的矛盾:一个反常的天真的编译器…很适合天真乖张的面试官。
等一下。面试官,他长得像这个人吗?
面试官本人没有通过这次面试已经够糟糕的了,如果这家公司的其他程序员"通过"了这个测试呢?
不,评估声明1 == 0和2 == 0应该同样迅速。我们可以想象糟糕的编译器实现,其中一个可能比另一个更快。但没有充分的理由解释为什么一个要比另一个快。
即使在某些模糊的情况下声明是正确的,程序员也不应该基于模糊(在本例中是令人毛骨悚然)的知识来进行评估。别担心这次面试,这里最好的办法就是走开。
免责声明:这不是原创的迪尔伯特卡通。这只是一个混搭。
- +卡通1张。没有足够的卡通
- 如果它也包含一个问题的答案,这将很有趣。
- 不,但实际上,我们可以很容易地想象,所有由严肃的公司编写的编译器都会生成合理的代码。让我们以"非优化案例"为例/o0,也许它最终会像安纳托利亚发布的那样。那么CPU的问题是,cmp操作数运行的周期比1到0的周期短吗?比2到0的周期短吗?一般来说,执行CMP需要多少个周期?它是根据位模式变化的吗?它们是不是更"复杂"的位模式会减慢cmp?我个人不知道。您可以想象一个超级白痴的实现检查从0到n(例如n=31)一点一点地进行。
- 这也是我的观点:对于1和200,cmp操作数应该同样快。也许我们可以想象白痴的实现,而事实并非如此。但是我们能想象一个非白痴的实现,其中while(1)比while(200)更快吗?同样,如果在一些史前时代,唯一可用的实现是这样的白痴,那么我们今天应该为它大惊小怪吗?我不这么认为,这是尖头上司的谈话,是一个真正的宝石!
- @v.ouddou"CMP操作数在1到0比2到0的周期内运行吗?"——不,您应该了解什么是周期。我个人不知道。你可以想象一个超级白痴的实现检查一点一点地从0到n级——或者另一种方式,仍然使面试官成为一个愚蠢的白痴。为什么要一点一点地检查呢?该实现可以是一个坐在盒子里的人,他决定在评估您的程序的过程中休息一下。
你的解释是正确的。这似乎是一个除了技术知识之外还考验你自信的问题。
顺便问一下,如果你回答
Both pieces of code are equally fast, because both take infinite time to complete
面试官会说
But while (1) can do more iterations per second; can you explain why? (this is nonsense; testing your confidence again)
所以像你那样回答,你节省了一些时间,否则你会浪费在讨论这个坏问题上。
以下是编译器在我的系统(MS Visual Studio 2012)上生成的示例代码,其中禁用了优化:
1 2 3 4 5 6 7
| yyy:
xor eax, eax
cmp eax, 1 (or 2, depending on your code)
je xxx
jmp yyy
xxx:
... |
启用优化后:
所以生成的代码是完全相同的,至少在优化编译器中是一样的。
- cmp eax, 1 (or 2, depending on your code)—这不完全正确。while的操作数是布尔类型,因此它不完全对应于程序集中整数的操作(尽管它可以,只要保留语义)。
- 这个代码就是编译器在我的系统上输出的代码。我没有弥补。
- 我没有说是你做的(好吧,你的编译器肯定没有输出括号中的部分),只是说与1、2、3或10000相比,while (some boolean value)逻辑没有1-1对应关系。如果编译器保留了功能性,则它可能会选择此实现。
- 您使用了什么编译器/编译设置?
- @Valekhalfheart更新了我的答案
- Icepack"while的操作数是布尔类型"——完全无意义。你是面试官吗?我建议你在提出这样的要求之前先熟悉C语言及其标准。
- "我没编好。"——请别理说废话的冰袋。c没有布尔类型(它在stdbool.h中有bool,但这不一样,while的语义早在它前面),while的操作数不是布尔类型、bool或任何其他特定类型。while的操作数可以是任何表达式…while在0上中断,并在非0上继续。
- "两段代码的速度都一样快,因为它们都需要无限的时间来完成",这让我想到了一些有趣的事情。终止无限循环的唯一方法是让带电粒子翻转一点,或者硬件故障:将语句从while (00000001) {}改为while (00000000) {}。越是正确的位,值翻转为错误的可能性就越小。遗憾的是,2也只有一个真正的位。然而,3的运行时间将明显更长。这也只适用于不总是优化它的编译器(vc++)。
- @jonathandickinson通过消除可能翻转/切换的值,有没有可能实现最小的优化?
- @ MR5没有。为了让一点翻转真正产生这样的结果,您将讨论数万年后的执行时间。只是一个思想实验。如果你来自一个不朽的种族,你可能想使用-1来防止位翻转影响你的程序。
- @jonathandickinson"你可能想使用-1"--我将使用一个编译器来优化常量,它几乎是所有常量。
- "while的操作数可以是任何表达式"--我应该添加"属于标量类型"。
- 我的私人棺材里还有一颗钉子
- @乔纳森迪金森,这让我问了那个愚蠢的问题,因为你说3比特比2比特长得多。
- 如果是自信测试,正确答案是什么?编码人员每天都要测试假设,过于自信会导致灾难。
- 你们都没说CPU,代码是一样的,但cmp操作呢?没有人说它有一个恒定的时间执行,不管操作数中的值是什么。你必须说出来,证明它,然后你的论点就结束了。编译程序是一回事,这很重要,但故事还没有到此为止。
- ^^^^胡扯^^^^
- 非最优版本是32位的,但是优化后的版本可能是64位代码,所以它可能会运行得慢一些,因为寄存器越大,JMP的运行时间就越长?-)
- 对我来说,自信地与面试官对抗似乎是一种冒险的策略。他可能只是在"考验你的信心",但在我看来,他更可能真的相信他所说的话。
- @Jimbalter:几年后又跳了进来,"C没有布尔类型(它在stdbool.h中确实有bool,但这不一样,而且语义早在它之前)"。从c99开始,c有一个布尔类型,称为_Bool。它没有在中定义,它是像int那样预先定义的。将bool定义为_Bool的别名。另一方面,相等运算符和比较运算符生成的结果是int类型,而不是_Bool类型,但我不认为这会导致_Bool不是布尔类型。
- @吉米巴特:我删除了多余的评论。
对这个问题最可能的解释是,面试官认为处理器会逐个检查数字的各个位,直到其达到非零值:
1 2
| 1 = 00000001
2 = 00000010 |
如果"是零?"算法从数字的右侧开始,必须检查每个位,直到它达到非零位为止,while(1) { }循环在每次迭代中必须检查两倍于while(2) { }循环的位。
这需要一个非常错误的关于计算机工作方式的心理模型,但它确实有自己的内在逻辑。一种检查方法是询问while(-1) { }或while(3) { }是否同样快,或者while(32) { }是否更慢。
- 我假设面试官的误解更像是"2是一个int,需要转换为布尔值才能用于条件表达式,而1已经是布尔值。"
- 更可能的是:stackoverflow.com/questions/24848359/&hellip;
- 如果比较算法从左边开始,则相反。
- +1.试图调试面试官。
- @ Pa?Loebermann如果它是一个大的endian架构呢?:)
- +我就是这么想的。你完全可以想象有人相信,从根本上讲,CPU的cmp算法是一种线性位检查,第一个差异就是早期循环退出。
- 不,最可能的解释是面试官是一个一点也不多想的傻瓜。
- 它似乎需要一个假定如下的心理模型:CPU时钟周期长度是电流通过距离执行最简单的逻辑操作所需的时间(比如通过XOR门的第一级)。当然,时钟周期长度实际上是一个基于CPU物理的任意时间约束,CPU制造商将根据这个量子建立一个不同时钟周期成本的指令目录。再想一想,也许心理模型只是CPU不能并行地对寄存器位执行电气操作?
- 这里有一个很好的关于等价函数的更新,当它与"grand and"结合时,会产生一个用于比较字节的电路:calculamus.org/logsoc03/bramki/loggates1.html
- 也许它是一台带有1位CPU的图灵机器?我是说面试官。
- 我甚至没有想过有人会那样想。任何一个合适的编译器都会发出只使用无条件分支进行无限循环的代码。我想的问题是,是否有编译器如此愚蠢,以至于它们只将while(1)和while(42)识别为无限循环习惯用法,而不是任何非零编译时间常数的一般情况。
- 根据analotlyg的回答,在调试模式下的msvc确实无法将其识别为无限循环,并使代码归零成为寄存器,然后将其与即时的1进行比较,这很有趣。但两者的代码大小和执行速度仍然相同。对于常见的简单指令,任何CPU上依赖于数据的执行时间都非常少。它确实发生在复杂的指令(如div中)。在某些CPU中,可能mul在某些设计中很常见,但与数据无关。(特别是在CPU不正常的情况下,调度很难,延迟可变。)
当然,我不知道这位经理的真正意图,但我提出了一个完全不同的观点:当雇佣一个新成员加入一个团队时,了解他对冲突情况的反应是很有用的。
他们把你逼入冲突。如果这是真的,他们是聪明的,问题是好的。对于某些行业,如银行业,将问题发布到堆栈溢出可能是拒绝的原因。
但我当然不知道,我只是提出了一个选择。
- 回答得很好!
- 这确实很好,但(2)对(1)显然是取自迪尔伯特漫画。它不可能由头脑清醒的人发明(不管怎样,有人怎么想出一个可能的写作方法?)如果你的假设是真的,你肯定会给出一个独特的问题,你可以谷歌搜索。就像"while(0xf00b442)慢于while(1)"一样,银行会如何发现inreviewer的问题?你认为他们是国家安全局的成员,有权接触到"核心"吗?
- 不,他们是白痴。唯一的冲突应该是如何以最少的损失从面试中解脱出来。
我想线索应该在"高级经理问"中找到。当他成为经理后,这个人显然停止了编程,然后他/她花了几年时间才成为高级经理。从没有对编程失去兴趣,但从那以后就再也没有写过一行。因此,正如一些答案所提到的,他的参考不是"任何一个合适的编译器",而是"这个人20-30年前工作过的编译器"。
当时,程序员花了相当大的一部分时间尝试各种方法,使他们的代码更快、更高效,因为"中央微型计算机"的CPU时间是如此宝贵。就像编写编译器的人一样。我猜他公司当时提供的唯一一个编译器是基于"经常遇到的可以优化的语句"进行优化的,并且在遇到一段时间(1)和评估所有其他内容(包括一段时间(2))时采取了一些捷径。有了这样的经验可以解释他的立场和对它的信心。
最好的聘用方法可能是让高级经理能够在你顺利引导他进入下一个面试主题之前,对你进行2-3分钟的"编程的好日子"的演讲。(好的时机在这里很重要——太快了,你打断了故事——太慢了,你被贴上了注意力不集中的标签)。在面试结束时一定要告诉他,你会非常有兴趣了解关于这个话题的更多信息。
- 我认为最好的办法是走开。
- 不同意。这是无害的管理无能。这种有害的东西更难看。
你应该问他是怎么得出这个结论的。在任何合适的编译器下,这两个编译器编译成相同的asm指令。所以,他也应该告诉你编译器的开始。即使如此,你也必须非常了解编译器和平台,才能做出一个理论上有教育意义的猜测。最后,实际上这并不重要,因为还有其他外部因素,比如内存碎片或系统负载,会比这个细节更影响循环。
- 如果面试官问为什么会发生这种情况,你很难把这个问题向他们反驳。
- @GKFX如果你给出了答案,他们告诉你你错了,你没有理由不能让他们解释原因。如果Anatolyg是正确的,这是对你自信的测试,那么你应该解释为什么你回答的方式和问他们同样的问题。
- 我的意思是作为你对他们说的第一件事。不能是"为什么X更快?"我不知道,为什么X更快?"很明显,回答正确后,你可以问。
为了这个问题,我应该补充一下,我记得来自C委员会的Doug Gwyn写道,一些没有优化器通过的早期C编译器将为while(1)生成一个汇编测试(与没有优化器通过的for(;;)相比)。
我会给采访者一个历史性的回答,然后说即使我对任何一个编译器这样做感到非常惊讶,一个编译器也可以:
- 如果没有优化器通过,编译器将为while(1)和while(2)生成一个测试。
- 优化器通过后,编译器将被指示优化(无条件跳转)所有while(1),因为它们被认为是惯用的。这将使while(2)处于测试状态,因此会在两者之间产生性能差异。
当然,我会向采访者补充一点,不考虑while(1)和while(2),同一构造是低质量优化的标志,因为它们是等效构造。
- +1为while(1)对for(;;)。我很高兴还有人还记得过去那些糟糕的日子:)
另一个问题是,看看你是否有勇气告诉你的经理他/她错了!以及你能多么温柔地交流。
我的第一直觉是生成程序集输出,向管理器显示任何合适的编译器都应该处理它,如果不这样做,您将提交它的下一个补丁:)
看到这么多人深入研究这个问题,就可以确切地说明为什么这很可能是一个测试,看看你想多快地微观优化事情。
我的回答是:这没什么关系,我宁愿把重点放在我们正在解决的业务问题上。毕竟,这是我的报酬。
此外,我会选择while(1) {},因为它更常见,其他队友也不需要花时间去弄明白为什么有人会选择比1更高的号码。
现在去写一些代码。;-)
- 除非你是为了优化一些实时代码而付费的,否则你只需要花费1或2毫秒就可以满足它的运行时间需求。当然,这是优化器的工作,有些人会说——也就是说,如果您的体系结构有一个优化器的话。
在我看来,这是一个伪装成技术问题的行为面试问题。有些公司会这样做——他们会问一个技术问题,这对于任何有能力的程序员来说都是相当容易回答的,但是当被采访者给出正确的答案时,采访者会告诉他们他们是错的。
公司想看看你在这种情况下会有什么反应。你是否因为自我怀疑或害怕打乱面试官而静静地坐在那里,不去强迫自己的回答是正确的?或者你愿意挑战一个你知道错误的权威人士?他们想知道你是否愿意为自己的信念辩护,以及你是否能以一种得体和恭敬的方式做到这一点。
如果你担心优化,你应该使用
因为没有测试。(犬儒模式)
- 这就是为什么聪明到使用的计算机while(2),叶间的优化,然后你到for (;;)简单的开关,当你准备好的绩效是一个升压。
我曾经在这种胡说八道的情况下对C和汇编代码进行编程。当它确实起了作用时,我们把它写在汇编中。
如果我被问到这个问题,我会重复唐纳德·克努斯1974年著名的关于过早优化的引述,如果采访者不笑,继续前进,我会走。
- 他说,"也给出了在既定的工程类学科(12 %的改善,不易通过,被冰缘和在同样的观点,我应该相信prevail进入软件工程",我认为你在冰unjustified。
也许面试官故意提出这样一个愚蠢的问题,想让你说3点:
基本推理。两个循环都是无限的,很难谈论性能。
了解优化水平。他想听听你的意见,如果你让编译器为你做任何优化,它将优化条件,特别是如果块不是空的。
了解微处理器架构。大多数架构都有一个特殊的CPU指令来与0进行比较(但不一定更快)。
从人们花在测试、证明和回答这个非常直截了当的问题上的时间和精力来看,我认为这两个问题都是通过问这个问题而变得非常缓慢的。
所以花更多的时间在上面…
"while(2)"是荒谬的,因为,
"while(1)"和"while(true)"在历史上用于生成无限循环,该循环期望在循环内部的某个阶段根据一定会发生的条件调用"break"。
"1"只是简单地存在,总是评估为真,因此,说"while(2)"和说"while(1+1==2)"一样愚蠢,也将评估为真。
如果你想完全愚蠢的话,就用-
1 2 3 4
| while (1 + 5 - 2 - (1 * 3) == 0.5 - 4 + ((9 * 2) / 4)) {
if (succeed())
break;
} |
我想你的编码员做了一个打字错误,但没有影响代码的运行,但如果他故意使用"2"只是为了让人觉得奇怪,那么在他把奇怪的sh放出来之前就解雇他!t通过你的代码使阅读和工作变得困难。
- 您所做的回滚将由一个非常高的rep用户恢复编辑。这些人通常非常了解政策,他们来这里是为了教你。我发现你文章的最后一行对你的文章没有任何帮助。即使我得到了明显丢失的笑话,我也会认为它太健谈,不值得再多读一段。
- @谢谢你的评论。我发现同一个人在我的帖子里编辑了很多内容,大部分都是为了删除署名。所以我认为他只是一个名誉恶棍,因此他的名声。在线论坛是否耗尽了语言和共同的礼貌,以至于我们不再签署我们的作品?也许我有点老了,但省略了问候和道别似乎很不礼貌。我们正在使用应用程序,但我们仍然是人类。
- 在堆叠交换,问候语,标语,谢谢等不受欢迎。帮助中心也提到了这一点,特别是在预期行为下。栈交换的任何一部分,甚至栈溢出,都不是一个论坛——它们是问答网站。编辑是其中的一个区别。此外,评论应该只提供对文章的反馈,而不是讨论(堆栈溢出聊天)。问答与论坛还有很多不同之处。更多关于帮助中心和元堆栈溢出的信息。
- 请注意,当您拥有超过2000个rep(编辑权限)时,您将无法从编辑中获得更多rep。当你怀疑为什么和编辑你不同意已经应用到你的文章,或者你看到某人的错误行为,问元栈溢出。如果编辑做了错误的事情,他们可能会被一个mod通知(也许他们没有意识到),甚至受到某种惩罚(如果行为是故意恶意的)。否则,您会得到一些指针来解释为什么编辑/行为是正确的。不必要的回滚会使修订历史变得混乱,并会使您陷入回滚战争。只有当确定编辑不正确时,我才会回滚。
- 最后,关于签名、问候……:不包括这些手续可能看起来很粗鲁,但它会增加信噪比,并且不会丢失任何信息。你可以选择任何你想要的昵称,那就是你的签名。在大多数地方,你也有你的化身。
- @帕莱克:我谦虚地承认,你们是对的,我错了,我明天会研究一下我是如何取消回退的。谢谢你的提醒。
- @美加:如上所述,谢谢和道歉。
- @埃克纳,我的编辑没有给我带来任何代表;我只是在清理你的帖子。我不仅删除了你的签名,还修复了我注意到的任何拼写/语法错误。如果有疑问,你应该像其他人那样做。你在这个网站上的任何地方看到一个人在他们的帖子中使用签名吗?这并不是因为他们很粗鲁,他们只是明白这里的惯例是不使用他们,这比你在维基百科文章中看到签名和问候语所期望的要多得多。我已经处理了回滚;在这一点上,我在执行大量编辑方面相当高效。
- @美加:明白。为我的错误判断道歉。很抱歉,你的工作做得不好。不会再发生了。谢谢您。
- 在你的情况下,你应该用!=代替==使之成为true。
- "在anatolyg:哈哈,你让我相信,没有双支票,哈哈!你应该看一遍,其正确。《数学杂志的左和右的评价:"每到1。
他们都是平等的——是一样的。
根据规范,任何非0的东西都被认为是正确的,因此即使没有任何优化,一个好的编译器也不会生成任何代码。暂时(1)或暂时(2)。编译器将为!= 0生成一个简单的检查。
- @Djechlin——因为它们都需要1个CPU周期。
- 编译器常量对其进行折叠,因此甚至不会在运行时对其进行计算。
这里有一个问题:如果你真的写了一个程序并测量它的速度,两个循环的速度可能会不同!为了进行一些合理的比较:
1 2 3 4 5
| unsigned long i = 0;
while (1) { if (++i == 1000000000) break; }
unsigned long i = 0;
while (2) { if (++i == 1000000000) break; } |
添加了一些打印时间的代码后,一些随机效应(比如循环如何定位在一行或两行缓存中)可能会产生影响。一个循环可能完全在一条缓存线内,或者在一条缓存线的开头,或者跨越两条缓存线。结果,不管面试官说的是什么,最快的都可能是最快的——纯属巧合。
最坏的情况:一个优化的编译器不知道循环是做什么的,但是发现在执行第二个循环时产生的值与第一个循环产生的值相同。并为第一个循环生成完整代码,但不为第二个循环生成完整代码。
- "缓存线"推理只能在缓存非常小的芯片上工作。
- 这不是重点。关于速度的问题假设"其他所有条件都相同"。
- @夏普图斯:错了。问题不在于代码是否适合缓存——当然,一个紧密的循环将适合。问题是处理器必须从内存中获取指令,通常是通过高速缓存,进入指令解码器。通常,指令解码器一次只能从一条缓存线中获取指令。因此,从一条缓存线中获取相同的10个字节的指令要比从两条缓存线中获取指令快。
- @吉米巴特:这不是速度问题,而是与面试官争论的问题。做一个实际的测试可能证明面试官是"正确的"——原因与他的论点无关,只是纯粹的巧合。如果你试图证明他是错的,这会让你陷入尴尬的境地。
- @我明白你的意思了。是的,这可能会有所不同。
- @撇开所有的逻辑论证不谈,你所说的巧合是值得一看的。但仍然盲目地相信这样一个案件的存在不仅是幼稚的,而且是愚蠢的。只要你不解释会发生什么,怎么发生,为什么会发生,这个答案是无用的。
- @"这不是速度问题"——我不会对使用不诚实的修辞技巧的人进行辩论。
- @用户568109:在agner.org/optimize下载agner fog的优化手册。一种优化技术是在缓存线中对齐循环,以最小化指令获取的数量。获取旧的PowerPC手册。G4处理器有一个分支目标缓存,在分支目标处保存两个指令字,如果一个循环由BTC中的64位和下一个缓存线中的128位组成,则执行速度最佳。这是足够的证据吗?
- @用户568109:然后是68020处理器,它实际上以不同的周期数执行相同的循环,这取决于处理器的不透明内部状态。我指的是同一个循环,不是具有相同指令的循环的副本。通常,每次迭代的周期数在从硬件中断返回后可能会改变。
- @Gnasher729您只是猜测缓存优化会使它以不同的方式运行。这会影响到他们两个,所以在性能上有什么区别。对于第二个例子,您是否认真地告诉我中断处理会影响性能并导致差异。我不相信你异想天开的比较感。让我们不要在做比较的方法和你的方法之间争论。
- @用户568109:我是认真地告诉你。也许我应该告诉你,我在70年代后期对代码进行了优化,在80年代后期对68020进行了优化,并对其进行了测量。在可以关闭中断的机器上。并且发现了两次迭代,有时是三次迭代,中断关闭,变量时间,中断打开。
- 在你的评论的第一部分:你似乎不擅长阅读。我说过优化缓存对齐可以提高代码的速度。结果是,具有非优化随机缓存对齐的代码可能以不同的速度运行,这取决于随机缓存对齐。例如,英特尔自己的《英特尔64和IA-32体系结构优化参考手册》就是这样告诉你的。
这取决于编译器。
如果对代码进行优化,或者对特定指令集使用相同数量的指令将1和2计算为真,则执行速度将相同。
在实际情况下,它的速度总是一样快,但是当计算结果不同时,可以想象一个特定的编译器和一个特定的系统。
我的意思是:这不是一个与语言相关的问题。
显而易见的答案是:正如贴出的,两个片段将运行一个同样繁忙的无限循环,这使得程序无限慢。
虽然将C关键字重新定义为宏在技术上具有未定义的行为,但这是我能想到的唯一一种快速实现代码片段的方法:您可以将此行添加到2个片段之上:
1
| #define while(x) sleep(x); |
它确实会使while(1)的速度是while(2)的两倍(或一半)。
- 语法错误,不;……
- "maxb:妈妈的宏观技巧必须contorted甚至更多。我认为新的版本,尽管不应该工作,保证和控制由C标准。
因为想要回答这个问题的人希望得到最快的循环,所以我会回答,正如其他答案中所述,这两个问题都被同样地编译成相同的汇编代码。不过,你可以向面试官建议使用"循环展开"的do while循环,而不是while循环。
小心:您需要确保循环至少总是运行一次。
循环内部应该有一个中断条件。
另外,对于这种循环,我个人更喜欢使用do while(42),因为除0之外的任何整数都可以完成该任务。
- 他为什么会suggest A do { } while循环?while(1)或(2)随)做同样的事情。
- 做一个额外的跳跃,而removes SO更好的绩效。大学课程的案例是你知道那会executed回路至少一次
我能想到while(2)会变慢的唯一原因是:
代码将循环优化为
cmp eax, 2
当减法发生时,基本上就是减法。
A.江户十一〔17〕号
而不是
b.江户十一〔18〕号
cmp只设置标志,不设置结果。因此,对于最低有效位,我们知道是否需要借用b,而对于a,在借用之前必须执行两次减法。
- 无论在这两种情况下,CMP都将占用1个CPU周期。
- 那个代码是不正确的。正确的代码会将2或1加载到寄存器eax中,然后将eax与0进行比较。
- @松开,这是一个很好的观点