假设我有以下C代码:
int i = 5;
int j = 10;
int result = i + j;
如果我多次循环,那么使用int result = 5 + 10
会更快吗?我经常创建临时变量以使我的代码更具可读性,例如,如果使用某个长表达式从某个数组中获取这两个变量来计算索引。这在C中是否表现不佳?其他语言怎么样?
答案 0 :(得分:83)
现代优化编译器应优化这些变量,例如,如果我们使用gcc
使用-std=c99 -O3
标记godbolt see it live constant propagation中使用以下示例( Optimizing C++ Code : Constant-Folding ):
#include <stdio.h>
void func()
{
int i = 5;
int j = 10;
int result = i + j;
printf( "%d\n", result ) ;
}
它将导致以下程序集:
movl $15, %esi
用于计算i + j
,这是{{3}}的形式。
注意,我添加了printf
以便我们产生副作用,否则func
会被优化为:
func:
rep ret
as-if规则允许这些优化,只需要编译器模拟程序的可观察行为。 C99标准部分5.1.2.3
程序执行草案中对此进行了介绍,其中包含:
在抽象机器中,所有表达式都按照指定的方式进行评估 语义学。实际的实现不需要评估部分内容 表达式,如果它可以推断出它的值没有被使用而且没有 产生所需的副作用(包括通过调用a引起的任何副作用) 功能或访问易失性对象。)
另见:{{3}}
答案 1 :(得分:29)
这对于优化编译器进行优化是一项简单的任务。它将删除所有变量并将result
替换为15
。
SSA form中的常量折叠几乎是最基本的优化。
答案 2 :(得分:11)
您给出的示例很容易让编译器进行优化。使用局部变量来缓存从全局结构和数组中提取的值实际上可以加快代码的执行速度。例如,如果您从for循环内的复杂结构中获取某些内容,而编译器无法进行优化,并且您知道该值没有更改,则局部变量可以节省相当多的时间。
您也可以使用GCC(其他编译器)生成中间汇编代码,并查看编译器实际执行的操作。
有关如何打开装配清单的讨论:Using GCC to produce readable assembly?
检查生成的代码并查看编译器实际正在做什么可能是有益的。
答案 3 :(得分:10)
虽然代码中的各种微不足道的差异会以轻微改善或恶化性能的方式扰乱编译器的行为,但原则上它是否应该使用像这样的临时变量而不会产生任何性能差异程序的含义没有改变。一个好的编译器应该以任何一种方式生成相同或可比较的代码,除非您有意建立优化关闭以使机器代码尽可能接近源(例如用于调试目的)
答案 4 :(得分:5)
当我尝试学习编译器的功能时,你遇到了同样的问题 - 你做了一个简单的程序来演示问题,并检查编译器的汇编输出,只检查意识到编译器已经优化了你试图让它消失的所有东西。您甚至可能会发现main()中的一个相当复杂的操作基本上被简化为:
push "%i"
push 42
call printf
ret
您的原始问题不是&#34; int i = 5; int j = 10...
会发生什么?&#34;但是,临时变量通常会产生运行时间的惩罚吗?&#34;
答案可能不是。但是,您必须查看特定的非平凡代码的程序集输出。如果你的CPU有很多寄存器,比如ARM,那么i和j很可能在寄存器中,就像那些寄存器直接存储函数的返回值一样。例如:
int i = func1();
int j = func2();
int result = i + j;
几乎可以肯定是完全相同的机器代码:
int result = func1() + func2();
我建议您使用临时变量,如果它们使代码更易于理解和维护,并且如果您真的想要收紧循环,那么您无论如何都要查看汇编输出以了解如何尽可能地提高性能。但如果没有必要,请不要牺牲可读性和可维护性几纳秒。