12

当我将-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"件事有效。

4

3 回答 3

9

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

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

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

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

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

于 2012-04-21T15:57:46.313 回答
7

您可能正在寻找链接时间优化(LTO),也就是整个程序优化。

于 2012-04-21T14:18:36.940 回答
1

由于您使用的是 GCC,因此您可以使用 C99inline函数说明符机制。这是来自 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 请注意,of 的定义fahr是外部定义,因为fahr也使用 extern 声明,但 cels 的定义是内联定义。因为cels具有外部链接并被引用,外部定义必须出现在另一个翻译单元中(见 6.9);内联定义和外部定义是不同的,都可以用于调用。

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

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

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


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

于 2012-04-21T19:47:56.203 回答