一位同事最近向我透露,我们的一个源文件在编译期间包含超过3,400个头文件。我们在一个构建中编译了超过1000个翻译单元,导致了巨大的性能损失,因为头文件肯定没有全部使用。
是否有任何静态分析工具能够阐明这样一个森林中的树木,特别是给我们决定我们应该努力淘汰哪些树木的能力?
在这里找到了一些关于包含头文件的成本的有趣信息(以及优化其包含的包含保护的类型),这些信息起源于这个问题。
gcc -w -H <file>
的输出可能是有用的(如果您解析它并放入一些计数),-w
在那里抑制所有警告,这可能难以处理。
来自gcc文档:
- h
打印所使用的每个头文件的名称,以及其他正常活动。每个名称都被缩进,以显示其深度即
#include
堆叠。预编译的头文件也会打印出来,即使被认定无效;无效的预编译头文件文件用...x
打印,有效文件用...!
打印。
输出如下所示:
. /usr/include/unistd.h
.. /usr/include/features.h
... /usr/include/bits/predefs.h
... /usr/include/sys/cdefs.h
.... /usr/include/bits/wordsize.h
... /usr/include/gnu/stubs.h
.... /usr/include/bits/wordsize.h
.... /usr/include/gnu/stubs-64.h
.. /usr/include/bits/posix_opt.h
.. /usr/include/bits/environments.h
... /usr/include/bits/wordsize.h
.. /usr/include/bits/types.h
... /usr/include/bits/wordsize.h
... /usr/include/bits/typesizes.h
.. /usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/include/stddef.h
.. /usr/include/bits/confname.h
.. /usr/include/getopt.h
. /usr/include/stdio.h
.. /usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/include/stddef.h
.. /usr/include/libio.h
... /usr/include/_G_config.h
.... /usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/include/stddef.h
.... /usr/include/wchar.h
... /usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/include/stdarg.h
.. /usr/include/bits/stdio_lim.h
.. /usr/include/bits/sys_errlist.h
Multiple include guards may be useful for:
/usr/include/bits/confname.h
/usr/include/bits/environments.h
/usr/include/bits/predefs.h
/usr/include/bits/stdio_lim.h
/usr/include/bits/sys_errlist.h
/usr/include/bits/typesizes.h
/usr/include/gnu/stubs-64.h
/usr/include/gnu/stubs.h
/usr/include/wchar.h
如果您使用的是gcc/g++, -M
或-MM
选项将输出一行您所查找的信息。(前者将包括系统头文件,而后者不包括。还有其他的变体;)
$ gcc -M -c foo.c
foo.o: foo.c /usr/include/stdint.h /usr/include/features.h
/usr/include/sys/cdefs.h /usr/include/bits/wordsize.h
/usr/include/gnu/stubs.h /usr/include/gnu/stubs-64.h
/usr/include/bits/wchar.h
您需要在开始时删除foo.o: foo.c
,但其余部分是文件所依赖的所有头的列表,因此编写一个脚本来收集这些并总结它们并不太难。
当然,这个建议只在Unix上有用,而且只有在没有其他人有更好的主意的情况下才有用。: -)
几件事——
-
使用"preprocess only"查看你的预处理器输出。
-
使用预编译头文件
-
gcc有-verbose和——trace选项,它们也显示完整的包含树,MSVC有/showIncludes选项,在高级c++属性页
另外,在Visual Studio中显示c++文件的#include层次
John Lakos的"Large Scale c++ Software Design"有一些工具可以提取源文件之间的编译时依赖关系。
不幸的是,他们在Addison-Wesley网站上的存储库已经消失了(以及AW的网站本身),但我在这里找到了一个tarball:http://prdownloads.sourceforge.net/introspector/LSC-rpkg-0.1.tgz?download
我在几次工作前发现它很有用,而且它的优点是免费的。
顺便说一句,如果你还没有读过Lakos的书,听起来你的项目会受益。(当前版本有点过时了,但我听说Lakos在2012年有另一本书出版)
GCC有一个-M
标志,它将输出给定源文件的依赖项列表。您可以使用这些信息来找出哪些文件具有最多的依赖项,哪些文件最依赖,等等。
查看手册页了解更多信息。-M
有几个变种
我个人不知道是否有一个工具会说"删除这个文件"。这真的是一个复杂的问题,取决于很多东西。查看包含语句的树肯定会让您发疯....那会让我发疯,也会毁了我的眼睛。有更好的方法来减少编译时间。
- 取消内联的类方法。
- 在定义它们之后,重新检查你的include语句并尝试删除它们。通常有帮助的是删除它们,然后重新开始。
- 倾向于尽可能使用前向声明。如果你在头文件中取消内联方法,你可以做很多。
- 将大的头文件分解成较小的文件。如果一个类在一个文件中被频繁使用,那么把它单独放在一个头文件中。
- 1000平移单位实际上不是很多。我们有1 -2万个。:)
- 如果你的编译时间仍然太长,得到Incredibuild。
我听说有一些工具可以做到这一点,但我没有使用它们。
我创建了一些工具https://sourceforge.net/p/headerfinder可能这是有用的。不幸的是,它是"自制"工具,有以下问题,
- 在Vb中开发。净
- 需要编译的源代码
- 非常慢,占用内存。
GCC有一个可以保存中间文件的标志(-save-temps)。这包括.ii文件,它们是预处理器的结果(因此在编译之前)。您可以编写一个脚本来解析它,并确定所包含内容的权重/成本/大小,以及依赖树。
我写了一个Python脚本来做这件事(公开在这里:https://gitlab.com/p_b_omta/gcc-include-analyzer)。