编译器是否会优化未使用的链接文件?

4

我正在编写一些代码,速度非常重要。我刚刚在编写测试用例后开始制作主二进制文件。对于我的测试运行器,我只是使用通配符将所有内容提供给链接器。(如下所示)

在我看来,链接是C++将各个部分粘合在一起的阶段-填充对函数的引用等,并将其全部放入二进制文件中。

# Do the linking for the test binary
$(BIN)test_cases: $(TEST)TestRunner.o
    $(CC) $(TEST)*.o $(SRC)*.o $(CPPUNITLINKS) $(MAINLINKS) -o $(BIN)test_cases

我的问题是,考虑到我想以任何可能的方式加速我的程序,是否最好只链接“主”二进制文件所需的最少文件?这样会导致更轻便的可执行文件或更快的程序吗?还是编译器已经有效地丢弃了它不需要的任何东西?


我认为链接所有的obj文件不会影响exe的性能。 - Matt
应该很容易尝试一个实验。写下“hello, world”,链接一堆不必要的.obj文件,看看可执行文件有多大。然后再重复一遍,但这次不使用.obj文件,看看可执行文件有多大。 - user888379
@user888379:由于(在现代操作系统中)整个可执行文件并没有被加载到内存中 - 只有实际使用的部分会被加载 - 因此具有大型可执行文件不会导致速度上的损失。 - Mats Petersson
当然,我可以测试这个,我可能会明确地链接到主二进制文件,并查看它的影响。但我的程序很难进行速度测试,因为其中有很多随机因素。我想我需要更多地了解链接过程,以便感觉自己在正确地处理事情! - joeButler
3个回答

3
当您将目标文件链接到程序中时,链接器将解析程序中的任何未解析符号。如果你想消除死代码(默认情况下gcc不会这样做),可以按照以下步骤操作:
  1. 使用 -fdata-sections-ffunction-sections 标志构建目标文件(有关详细信息,请参阅 GCC 手册);
  2. 使用优化标志 -Wl,--gc-sections 链接目标文件,该标志告诉链接器丢弃未引用的部分。
注意:只有未使用的 static 函数才会自动剥离。
理论上冗余符号的存在仅影响生成程序的大小。然而,我看过一些帖子,人们报告在剥离死代码后性能提高了 1% 到 2%。当然,要注意代码库必须足够大才能注意到这种效果。
请注意,有时此方法可能无法正常工作。例如,在某些系统上,我遇到过崩溃或链接问题,这可能是由于实现此功能的错误导致的。
此外,请不要认为这些标志在每种情况下都可以提高性能和/或大小。有很好的理由为什么此功能通过标志打开而不是默认存在。实际上,有时链接器可能会创建更大的目标文件和可执行文件和/或更慢的代码,更不用说您肯定也会遇到调试问题了。
最后,当使用此功能时,请非常谨慎,并按照其他答案中推荐的建议在剖析代码之前和之后进行分析。
最后,如果您真正想要速度,可以查看我的关于某些有用的 GCC 优化标志的其他回答
最后但并非最不重要的是,所谓的链接时优化(LTO)是一个巨大且具有前途的新概念,已经引入了 GCC,并且最近变得越来越稳定。相应的标志是 -lto,请参见 此处此处 以获取更多信息。尽管现在它可以使用,但在某些平台上不是完美的。例如,在 Windows 上,GCC 端口 MinGW / MinGW-w64 仍在努力使 LTO 支持达到生产质量。

谢谢,我一定会尝试这个! - joeButler

1

文件数对于二进制文件的生成时间影响较小,除非文件实在是太大。当然,如果您想改善编译程序的时间,减少目标文件数量就是缩短构建时间的一个步骤。

程序执行所需的时间很大程度上取决于实际执行的代码。如果有0、1、3、5、20、100或10000个未被调用的函数,不会产生明显的差异。

理解为什么代码运行缓慢(如果确实运行缓慢 - 可能只是因为它需要这么长时间来执行您要求的工作)的关键是使用一个叫做分析器的工具。有许多选项可供选择,它们都基本上做相同的事情。在非常基本的层面上,分析器将告诉您在每个函数中花费了多少时间,这反过来将告诉您应该集中精力的地方。指令分析器将允许您深入到单个指令,以查看编译器已经完成了什么,并在函数内部花费了多少时间。


1
优化程序的第一步是进行性能分析(PROFILE)。一个好的性能分析将显示程序中每个函数使用的时间。
通过性能分析数据,找到被调用最多或花费最多时间的函数。这些函数是你应该集中优化的函数。
在优化时,按需求进行优化(例如删除某些内容),然后按设计进行优化(选择不同的算法、删除函数),然后按编码进行优化(重写更高效的代码,如减少分支和跳转),最后使用特定于平台的代码进行优化(专门的汇编指令)。编码优化的捷径是告诉编译器使用最大速度的最大优化。
如果要使用动态或共享库,请将经常使用的函数放入同一个库中。这样操作系统只需要加载较少的库。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接