识别编译速度慢的函数

时间:2015-03-07 19:56:44

标签: c++ performance gcc boost compilation

我有一些需要很多编译的cpp文件。它们包含一些基本的类/代码,带有一些模板,但没有什么可以证明编译时间大约几十秒。

我确实使用了几个外部库(boost / opencv)

这就是gcc所说的编译时间。如何找到可怕的编译时间的库/包含/函数调用?

Execution times (seconds)
 phase setup             :   0.00 ( 0%) usr   0.00 ( 0%) sys   0.01 ( 0%) wall    1445 kB ( 0%) ggc
 phase parsing           :   6.69 (46%) usr   1.61 (60%) sys  12.14 (47%) wall  488430 kB (66%) ggc
 phase lang. deferred    :   1.59 (11%) usr   0.36 (13%) sys   3.83 (15%) wall   92964 kB (13%) ggc
 phase opt and generate  :   6.25 (43%) usr   0.72 (27%) sys  10.09 (39%) wall  152799 kB (21%) ggc
 |name lookup            :   1.05 ( 7%) usr   0.28 (10%) sys   2.01 ( 8%) wall   52063 kB ( 7%) ggc
 |overload resolution    :   0.83 ( 6%) usr   0.18 ( 7%) sys   1.48 ( 6%) wall   42377 kB ( 6%) ggc
...

Profiling the C++ compilation process涉及识别慢速文件,但我需要更细粒度的信息才能找到罪魁祸首

(其他文件/项目以毫秒/秒编译,所以它不是计算机资源的问题。我使用gcc 4.9.1)

2 个答案:

答案 0 :(得分:17)

基本上有两件事导致编译时间过长:包含太多内容和模板太多。

当你包含太多标题并且这些标题包含了太多自己的标题时,它只意味着编译器有很多工作要做,以加载所有这些文件,它将花费大量的时间关于它必须对所有代码执行的处理过程,无论它是否实际使用,如预处理,词法分析,AST构建等等。当代码分布在大量的代码上时,这可能特别成问题小标题,因为性能非常受I / O限制(大量时间浪费只是从硬盘中读取和读取文件)。不幸的是,Boost库往往以这种方式结构化。

以下是解决此问题的几种方法或工具:

  • 您可以使用" include-what-you-use"工具。这是一个基于Clang的分析工具,它基本上会查看您在代码中实际使用的内容,以及这些内容来自哪些标题,然后使用前向声明来报告您可以通过删除某些不必要的包含而进行的任何潜在优化,或者可能取代更广泛的"一体化"带有更细粒度标题的标题。
  • 大多数编译器都有转储预处理源的选项(在GCC / Clang上,它是-E-E -P选项,或者只是使用GCC的C预处理程序{{1直接)。您可以获取源文件并注释掉不同的include语句或include语句组,并转储预处理的源以查看这些不同标头所引入的代码总量(并且可以使用行计数命令,如{{1} })。这可以帮助您识别要处理的大量代码行,哪些标题是最严重的违规者。然后,您可以看到您可以做些什么来避免它们或以某种方式缓解问题。
  • 您还可以使用预编译的标头。这是大多数编译器支持的功能,您可以使用它来指定某些标头(特别是包含在内的"一体化"标头)以进行预编译,以避免为每个源文件重新解析它们。包括他们。
  • 如果您的操作系统支持,您可以使用ram-disk代码和外部库的标题。这基本上占用了你的部分RAM内存,使它看起来像普通的硬盘/文件系统。这可以通过减少I / O延迟来显着减少编译时间,因为所有头文件和源文件都是从RAM内存而不是实际硬盘中读取的。

第二个问题是模板实例化。在GCC的时间报告中,应该在模板实例化阶段报告某个时间值。如果该数字很高,那么只要代码中涉及大量模板元编程,就会出现这种情况,那么您将需要处理该问题。有很多原因可以解释为什么一些模板繁重的代码编译速度很慢,包括深度递归的实例化模式,过于花哨的Sfinae技巧,滥用类型特征和概念检查,以及旧的时尚过度设计的通用代码。但是也有一些简单的技巧可以修复很多问题,例如使用未命名的命名空间(以避免浪费所有时间浪费生成实例化的符号,而这些符号实际上不需要在翻译单元之外可见)和专门化类型特征或概念检查模板(基本上是#34;短路"很多花哨的元编程进入它们)。模板实例化的另一个潜在解决方案是使用" extern templates" (来自C ++ 11)控制应该实例化特定模板实例化的位置(例如,在单独的cpp文件中),并避免在它使用的任何地方重新实例化。

以下是一些帮助您识别瓶颈的方法或工具:

  • 您可以使用" Templight"分析工具(及其辅助" Templight-tools"用于处理跟踪)。这又是一个基于Clang的工具,可以用作Clang编译器的替代品(该工具实际上是一个仪表化的完整编译器),它将生成编译期间发生的所有模板实例化的完整配置文件。 ,包括在每个上花费的时间(以及可选的内存消耗估计,尽管这会影响时序值)。稍后可以将跟踪转换为Callgrind格式并在KCacheGrind中可视化,只需阅读templight-tools page上的描述即可。这基本上可以像典型的运行时分析器一样使用,但用于在编译模板密码时分析时间和内存消耗。
  • 寻找最严重的违规者的一种更基本的方法是创建测试源文件,实例化您怀疑负责长编译时间的特定模板。然后,你编译这些文件,计时,并尝试按照自己的方式(可能采用二元搜索方式)对付最严重的罪犯。

但即使有这些技巧,识别模板实例化瓶颈比实际解决它们更容易。所以,祝你好运。

答案 1 :(得分:1)

如果没有关于如何组织和构建源文件的信息,这是无法完全回答的,所以只是一些一般性的观察。

  1. 模板实例化会大大增加编译时间,特别是如果在多个源文件的每个文件中为多个不同类型/参数实例化复杂模板。显式模板实例化的方案(即确保模板仅在少数源文件而不是所有源文件中实例化)可以减少在这种情况下的编译时间(以及链接时间和可执行文件大小)。您需要阅读编译器文档以了解如何执行此操作 - 默认情况下不一定会发生这种情况,并且可能意味着重构代码以支持它。
  2. 许多源文件中#include d的头文件,无论是否需要,都会增加编译时间。我看到一个案例,其中一个团队成员写了"globals.h" #include d所有内容,#include d到处都是 - 并且构建时间(在一个大型项目中)增加了一个大小。这是双重打击 - 每个源文件的编译时间都会增加,并且会乘以直接或间接#include该标头的源文件数。如果启用“预编译头”等功能会导致第二次和后续版本的构建时间加快,这可能是一个贡献者。 (您可以将预编译的头文件视为此解决方案,但请记住使用它们还有其他权衡因素。)
  3. 如果您使用的是外部库,请检查以确保它们是 在本地安装和配置。编译过程 默默地在互联网上寻找一些组件(例如a 某些远程服务器上的硬编码头文件名将变慢 事情相当大。你会惊讶于第三方图书馆经常出现这种情况。
  4. 除此之外,找到问题的技术取决于构建过程的结构。

    如果您正在使用分别编译源文件的makefile(或其他方法),则使用某种方式为单个编译和链接命令计时。请记住,它可能是主导的链接时间。

    如果您正在使用单个编译命令(例如,在一个命令中对多个文件调用gcc),则将其分解为每个源文件的各个命令。

    一旦你隔离了哪个源文件(如果有的话)是罪犯,那么有选择地从中删除一些部分以找出其中的哪个代码是问题。正如Yakk在评论中所说,使用“二分搜索”来消除文件中的功能。我建议先删除整个函数(缩小到有问题的函数),然后在违规函数中使用相同的技术。

    它有助于构建代码,因此每个文件的函数数量相当小。这减少了重建大文件的需要,以便对一个功能进行微小更改,并有助于在将来更轻松地隔离这些问题。