假设我有以下C代码:
int i = 5;
int j = 10;
int result = i + j;
如果我要遍历多次,使用起来会更快int result = 5 + 10
吗?我经常创建临时变量以使我的代码更具可读性,例如,如果两个变量是使用某个长表达式从某个数组中获取的,以计算索引。这在C语言中是不好的表现吗?那其他语言呢?
假设我有以下C代码:
int i = 5;
int j = 10;
int result = i + j;
如果我要遍历多次,使用起来会更快int result = 5 + 10
吗?我经常创建临时变量以使我的代码更具可读性,例如,如果两个变量是使用某个长表达式从某个数组中获取的,以计算索引。这在C语言中是不好的表现吗?那其他语言呢?
Answers:
一个现代的优化编译器应该将那些变量优化掉,例如,如果我们在godbolt中gcc
使用以下示例并使用-std=c99 -O3
标志(请参见live):
#include <stdio.h>
void func()
{
int i = 5;
int j = 10;
int result = i + j;
printf( "%d\n", result ) ;
}
它将导致以下组装:
movl $15, %esi
对于计算i + j
,这是恒定传播的形式。
注意,我添加了,printf
这样我们就有了副作用,否则func
将被优化为:
func:
rep ret
在as-if规则下允许进行这些优化,该规则仅要求编译器模拟程序的可观察行为。C99标准草案的5.1.2.3
程序执行部分对此进行了介绍,其中说:
在抽象机中,所有表达式均按语义指定的方式求值。如果实际实现可以推断出未使用表达式的值并且没有产生所需的副作用(包括由调用函数或访问易失性对象引起的副作用),则无需评估表达式的一部分。
另请参见:优化C ++代码:恒定折叠
gcc
使用中gcc -S -o asm_output.s app.c
您给出的示例易于编译器优化。使用局部变量缓存从全局结构和数组中提取的值实际上可以加快代码的执行速度。例如,如果要从for循环内的复杂结构中获取某些内容,而编译器无法对其进行优化,并且您知道该值没有改变,则局部变量可以节省大量时间。
您可以使用GCC(也可以使用其他编译器)生成中间汇编代码,并查看编译器的实际操作。
这里讨论了如何打开程序集列表:使用GCC生成可读的程序集?
检查生成的代码并查看编译器的实际操作可能是有益的。
尽管与代码的各种琐碎差异会以轻微改善或降低性能的方式干扰编译器的行为,但原则上,只要您不使用程序的含义,则是否使用此类临时变量都不会对性能造成任何影响改变了。一个好的编译器应该以任何一种方式生成相同或可比较的代码,除非您有意进行优化以使机器代码与源代码尽可能接近(例如出于调试目的)。
int a = arr[i]
会使编译器在函数调用和其他指针写入之间将值保存在寄存器中。
当您尝试学习编译器的功能时,您遇到的问题与我一样-您编写了一个简单的程序来演示该问题,并检查编译器的程序集输出,只是意识到编译器已经优化了所有内容您试图使它消失。您可能会发现main()中甚至一个相当复杂的操作都可以简化为:
push "%i"
push 42
call printf
ret
您最初的问题不是“会发生什么int i = 5; int j = 10...
?” 但是“临时变量通常会导致运行时损失吗?”
答案可能不是。但是您必须查看特定的非平凡代码的程序集输出。如果您的CPU有很多寄存器(如ARM),则i和j很可能位于寄存器中,就像这些寄存器直接存储函数的返回值一样。例如:
int i = func1();
int j = func2();
int result = i + j;
几乎可以肯定是与以下机器代码完全相同的机器代码:
int result = func1() + func2();
我建议您使用临时变量,如果它们使代码更易于理解和维护,并且如果您确实想收紧循环,则无论如何都要研究汇编输出以找出如何提高性能的方法。可能。但是,如果没有必要,不要在几纳秒内牺牲可读性和可维护性。
main()
对编译时常量进行运算的函数。例如,下面是一个简单的示例,该示例求和一个浮点数组,并使用Godbolt的gcc asm输出: goo.gl/RxIFEF
i
和j
。我的示例是makei
和j
function参数,因此它们只是在函数代码开头的寄存器中坐在那里。
int result = 15 ;