我和朋友讨论这个愚蠢的论点,并且需要一个权威性的词语。
我有这两个片段,想知道哪一个更快? [A或B]
(假设编译器没有优化任何东西)
[A]
if ( foo () );
[B]
int t = foo ();
if ( t )
编辑:伙计们,这对你来说可能看起来很愚蠢,但我有一个硬件工程师朋友,他认为即使没有优化(带任何处理器,任何处理器)编译器对)CASE B总是更快,因为它不会从前一条指令获取内存,而是通过绕过该数据直接访问公共数据总线的结果(记住5级流水线)。
虽然 My Argument 是这样的,没有编译器通知要复制或检查多少数据,但是不可能这样做(你必须转到内存来获取数据,没有编译器优化那个)
答案 0 :(得分:13)
将[B]转换为[A]所需的“优化”非常简单(特别是如果t
未在其他任何地方使用),编译器可能甚至不会调用这是一个优化。当然,无论是否明确启用优化,它都可能是它所做的事情。
要告诉的唯一方法是让编译器为两个代码位生成汇编源列表,然后进行比较。
答案 1 :(得分:4)
执行摘要
我们谈论的是纳秒。在那段时间里,光线移动了30厘米。
有时,如果你真的很幸运,[A]会更快
旁注:[B]可能有不同的含义
如果foo
的返回类型不是int
,而是对int
和bool
进行隐式转换的对象,则会执行不同的代码路径。一个可能包含Sleep
。
假设函数返回int:
取决于编译器
即使受到“无优化”的限制,也无法保证生成的代码的外观如何。 B可能快10倍,编译器仍然符合要求(你很可能不会注意到)。
取决于硬件
根据您的体系结构,无论您的编译器尝试了多少,生成的代码都可能没有区别。
假设在现代x86 / x64架构上使用现代编译器:
在典型的编译器上,差异至多是微不足道的
将t
存储在堆栈变量中,两个额外的堆栈负载通常需要2个时钟周期(在我的CPU上不到一纳秒)。与“周围成本”相比,这是微不足道的 - 对foo
的调用,foo
本身的成本以及分支。使用完整堆栈帧的未优化调用很容易花费20.200个周期,具体取决于patform。
用于比较:不在第一级缓存中的单个内存访问的周期成本(大约:第二级100个周期,主要1000个,磁盘数十万个)
......甚至不存在
即使你的编译器没有优化,你的CPU也可能。由于配对/微码生成,循环成本实际上可能是相同的。
答案 2 :(得分:3)
对于记录,gcc在使用特别禁用的优化(-O0
)进行编译时,会为两个输入生成不同的代码(在我的情况下,foo
的正文为return rand();
所以结果不会在编译时确定。)
没有临时变量t
:
movl $0, %eax
call foo
testl %eax, %eax
je .L4
/* inside of if block */
.L4:
/* rest of main() */
这里,foo
的返回值存储在EAX寄存器中,寄存器针对自身进行测试以查看它是否为0,如果是,则跳过if块的主体。 / p>
使用临时变量t
:
movl $0, %eax
call foo
movl %eax, -4(%rbp)
cmpl $0, -4(%rbp)
je .L4
/* inside of if block */
.L4:
/* rest of main() */
这里,foo
的返回值存储在EAX寄存器中,然后压入堆栈。然后,将堆栈上的位置内容与文字0进行比较,如果它们相等,则跳过if块的主体。
因此,如果我们进一步假设处理器在为此生成微码时没有进行任何“优化”,那么没有临时的版本应该更快几个时钟周期。它不会快得多,因为即使具有临时的版本涉及堆栈推送,当比较指令在字之后立即执行时,堆栈值几乎肯定仍然在处理器的L1缓存中,因此不会是一次往返RAM。
当你打开任何优化级别时,代码变得相同,甚至是-O1
,并且编译任何非常重要的东西,以至于他们关心所有优化关闭的少数时钟周期?
编辑:关于您有关硬件工程师朋友的更多信息,我无法看到如何访问L1缓存中的值更快直接注册。如果值永远不会离开管道,我可以看到它只是快速,但我看不到它更快,特别是因为它仍然必须执行除了比较之外,还有movl
条指令。但是告诉他上面的汇编代码并询问他的想法;它比以C语言讨论问题更有成效。
答案 3 :(得分:2)
它们可能都是一样的。在任何一种情况下,int都将存储到寄存器中。
答案 4 :(得分:0)
这实际上取决于编译器的构建方式。但我认为在大多数情况下,A会更快。原因如下:
在B中,编译器可能不会发现t是否再次被使用,因此它将被强制保留if语句之后的值。这可能意味着将其推入堆栈。
答案 5 :(得分:-1)
A可能会快一点,因为它不会进行变量赋值。我们所谈论的差异太小而无法衡量。