当我一步编译所有内容时,GCC 可以更好地优化事情吗?

Posted

技术标签:

【中文标题】当我一步编译所有内容时,GCC 可以更好地优化事情吗?【英文标题】:Can GCC optimize things better when I compile everything in one step? 【发布时间】:2012-04-21 14:14:23 【问题描述】:

当我将 -O2 标志传递给 gcc 时,gcc 会优化代码,但我想知道如果我将所有源文件编译为目标文件然后链接它们,它实际上能做到这一点。

这是一个例子:

// in a.h
int foo(int n);

// in foo.cpp
int foo(int n) 
  return n;


// in main.cpp
#include "a.h"
int main(void) 
  return foo(5);


// code used to compile it all
gcc -c -O2 foo.cpp -o foo.o
gcc -c -O2 main.cpp -o main.o
gcc -O2 foo.o main.o -o executable

通常,gcc 应该内联foo,因为它是一个小函数,而-O2 启用-finline-small-functions,对吗?但是在这里,gcc 在创建目标文件之前只看到foomain 的代码,所以不会有这样的优化,对吧?那么,这样编译真的会让代码变慢吗?

不过,我也可以这样编译:

gcc -O2 foo.cpp main.cpp -o executable

这样会更快吗?如果没有,这样会更快吗?

// in foo.cpp
int foo(int n) 
  return n;


// in main.cpp
#include "foo.cpp"
int main(void) 
  return foo(5);

编辑:我查看了objdump,它的反汇编代码显示只有#include "foo.cpp" 有效。

【问题讨论】:

将小函数放入.h文件中,注解为inline。 (虽然为了完全通用,您还需要使用我忘记的语法定义“hashome”属性。) @HotLicks:你的意思是,把函数包括它们的主体,而不仅仅是头文件,放到 .h 文件中? 是的,这就是方法上的“内联”注释的目的。 【参考方案1】:

看来您自己重新发现了关于 C 和 C++ 使用的单独编译模型的问题。虽然它确实降低了内存要求(这在创建它时很重要),但它通过只向编译器公开最少的信息来实现这一点,这意味着无法执行一些优化(如这个)。

较新的语言,其模块系统可以根据需要公开尽可能多的信息,如果模块进入下一个 C++ 版本,我们可以希望利用这些好处...

与此同时,最简单的方法是链接时间优化。这个想法是您将在每个 TU(翻译单元)上执行尽可能多的优化以获得目标文件,但您还将使用 IR(中间表示)丰富传统的目标文件(包含程序集),编译器使用它来优化) 用于部分或全部功能。

当调用链接器将这些目标文件合并在一起时,它不仅将文件合并在一起,还会合并 IR 表示,重新执行一些优化通道(常量传播、内联,...),然后创建自行组装。这意味着它不仅仅是一个链接器,它实际上是一个后端优化器。

当然,就像所有优化过程一样,这也是有代价的,因此需要更长的编译时间。另外,这意味着编译器链接器都应该被传递一个特殊的选项来触发这个行为,在gcc的情况下,它将是-lto-O4

【讨论】:

【参考方案2】:

您可能正在寻找Link-Time Optimization (LTO),也就是整体程序优化。

【讨论】:

可能值得一试,但我真的不喜欢告诉人们使用非标准 gcc 来获得快速机器代码... @thejh:仔细阅读,分支已经合并到主干,现在是大家都知道和使用的gcc的一部分。【参考方案3】:

由于您使用的是 GCC,因此您可以使用 C99 inline 函数说明符机制。这来自 ISO/IEC 9899:1999。

§ 6.7.4 函数说明符

语法

¶1 函数说明符:

      inline

约束

¶2 函数说明符只能用于函数标识符的声明中。

¶3 具有外部链接的函数的内联定义不应包含 具有静态存储持续时间的可修改对象,并且不应包含对 具有内部链接的标识符。

¶4 在托管环境中,inline 函数说明符不应出现在声明中 main.

语义

¶5 使用inline 函数说明符声明的函数是内联函数。这 函数说明符可能出现多次;行为与出现时相同 只有一次。使函数成为内联函数表明对该函数的调用应为 尽可能快。118) 此类建议的有效程度是 实现定义。119)

¶6 任何具有内部链接的函数都可以是内联函数。对于具有外部功能的 链接,以下限制适用: 如果函数声明为 inline 函数说明符,那么它也应该在同一个翻译单元中定义。如果所有的 翻译单元中函数的文件范围声明包括 inline 函数 没有extern 的说明符,则该翻译单元中的定义是内联 定义。内联定义不为函数提供外部定义, 并且不禁止在另一个翻译单元中进行外部定义。内联定义 提供了外部定义的替代方案,翻译人员可以使用它来实现 在同一个翻译单元中对函数的任何调用。未指定是否调用 函数使用内联定义或外部定义。120)

¶7 示例带有外部链接的内联函数的声明可能导致外部 定义,或仅在翻译单元内可用的定义。文件范围声明 extern 创建一个外部定义。以下示例显示了整个翻译单元。

inline double fahr(double t)

    return (9.0 * t) / 5.0 + 32.0;

inline double cels(double t)

    return (5.0 * (t - 32.0)) / 9.0;

extern double fahr(double); // creates an external definition
double convert(int is_fahr, double temp)

    /* A translator may perform inline substitutions */
    return is_fahr ? cels(temp) : fahr(temp);

¶8 请注意,fahr 的定义是外部定义,因为 fahr 也是用 extern 声明的,但是 cels 的定义是内联定义。因为cels 具有外部链接并被引用,所以 外部定义必须出现在另一个翻译单元中(见 6.9);内联定义和外部 定义是不同的,任何一个都可以用于调用。

118) 例如,通过使用通常的函数调用机制的替代方案,例如“内联 替换”。内联替换不是文本替换,也不是创建新函数。 因此,例如,在函数体中使用的宏的扩展使用 它在函数体出现时的定义,而不是函数被调用的位置;和 标识符指的是正文出现的范围内的声明。同样,该函数具有 单个地址,不管除了外部出现的内联定义的数量 定义。

119) 例如,一个实现可能永远不会执行内联替换,或者可能只执行内联 替换 inline 声明范围内的调用。

120) 因为内联定义不同于相应的外部定义和任何其他定义 其他翻译单元中对应的内联定义,所有对应的对象都有静态存储 持续时间在每个定义中也是不同的。


请注意,在标准化之前,GCC 在 C 中也有 inline 函数。如果您需要该符号,请阅读 GCC 手册了解详细信息。

【讨论】:

以上是关于当我一步编译所有内容时,GCC 可以更好地优化事情吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何从GCC /铿锵声组件输出中消除“噪音”?

嵌入式 GCC 优化魔法

GCC 优化标志

为啥编译器可以比普通函数更好地优化 lambda?

宙斯 - 成分 - 错误

如何在 Visual Studio 中最好地使用 GCC