g++在多个文件上比使用谷歌模拟的单片单个文件慢得多
g++ much slower on multiple files vs. monolithic single file using Google mock
我遇到了一个似乎与 g++ 相关的问题。基本上,当 g++ 被拆分为多个文件而不是单个整体文件时,编译程序需要花费更多的时间。事实上,如果你把各个文件放在一起并编译它,它的运行速度比你在 g++ 命令行上列出单个文件要快得多。例如,对于 9 个文件,编译需要 1 分 39 秒;当我将它们放在一起时,编译只需要 13 秒。我试过使用strace
但它只是卡在cc1plus
;当我使用-f
选项时,我仍然无法理清导致问题的原因。
我已经隔离了问题。以下是重现它的方法。我写了一个非常简单的程序,如下所示:
void func_01(int i)
{
int j;
volatile int *jp;
jp = &j;
for (; i; i--) ++*jp;
}
void call_01(void)
{
func_01(10000);
}
int main(int argc, char *argv[])
{
call_01();
}
然后我复制了它,删除了主要数字并替换了增加的数字,999 次。然后我构建了:
% time g++ -c test*.cpp
real 0m18.919s
user 0m10.208s
sys 0m5.595s
% cat test*.cpp > mon.cpp
% time g++ -c mon.cpp
real 0m0.824s
user 0m0.776s
sys 0m0.040s
因为我打算扩展到数百个比这复杂得多的文件,所以缩短构建时间很重要。谁能帮助解释为什么会发生这种情况,或者提供一个不那么粗暴的解决方法?我认为这部分与预处理器和包含保护带来的节省有关,因为如果我包含一个文件,时间差会显着增加(一种情况下是五倍),但它仍然保持,没有包含,与整体文件一起快了二十倍。
g++ 的版本是 4.4.2,但我检查了最新版本 8.2.0,它也存在在那里。
有两种不同的效果:
编译器调用开销:编译器是复杂的可执行文件,有时它们甚至被拆分为前端可执行文件和后端可执行文件,前端为每个单独的源文件生成后端,即使所有源文件都传递到前端的同一编译器调用。例如,Gcc和llvm就是这样做的。
- 指定
g++ -v
以查看这些冗余编译器调用。这回答了您的主要问题,我想到为什么即使没有头文件也会发生这种情况。
从头开始一遍- 指定
又一遍地解析和编译相同的头文件所带来的开销。在实际示例中,此标头开销将比编译器调用本身重要得多。
因为如果我甚至包含一个文件,时差就会大大增加(一种情况下是五倍)
是的!这也可能慢 1000 倍而不是 5 倍。对于模板密集型代码,编译器需要在编译时做很多事情。
拆分多个源文件时速度变慢,特别是对于C++代码,因为C++是标头密集型的。所有源*.cpp
都是单独编译的,并且它们包含的所有标头都以冗余方式包含在每个单独的源文件中。
现在,如果您将所有源文件放在一起,则正如您所说,由于包含保护,所有标头仅解析一次。由于编译器花费了很大一部分时间来解析和编译标头,因此这非常重要,特别是对于模板密集型代码(例如,使用 STL 就足够了)。
手写C++源代码以及生成的C++源代码的源文件数是以下各项之间的权衡:
我的完整重建时间很快,但我的增量重建时间很慢。
- 当您只有一个源文件(即 *.cpp 个文件)或很少的源文件时,就是这种情况。
我的完整构建时间很慢,但我的增量构建时间很快。
- 当您有大量小源文件(即 *.cpp 文件)时,就是这种情况。
(无论如何,头文件的数量并不重要(除非你总是拉入太多冗余的东西。这是关于编译器调用的数量,即 *.cpp 或 *.o 文件的数量。
对于 1. 从头开始的完整编译时间很短,因为编译器只看到一次所有标头,这在C++中非常重要,尤其是对于基于模板的仅标头(或标头密集型)库,如 STL 或 boost。
对于 2. 单个编译时间很快,因为当数百个文件中只有一个文件更改时,*.cpp 文件中只有很少的代码需要编译。
这在很大程度上取决于您的用例。
如果您生成C++代码,则应向生成器添加一个选项,以允许用户选择进行此权衡的方式。
我相信在这种情况下,大部分开销来自打开和关闭文件。在这两种情况下,您都有一个进程来完成这项工作。
我与将结果转储到/dev/null 的cat
程序进行了比较。对于cat test_*.cpp >/dev/null
,它需要~0.008s,对于cat mon.cpp >/dev/null
~0.001s。对于 10 个文件,这几乎是 999 倍的差异。除此之外,编译器还必须为其编译的每个文件设置一些内部管理,对于大型整体情况,这些管理仅执行一次。
但是,正如其他人已经回答在构建系统(如make或ninja)中设置构建时,与单文件相比,仅触摸多个文件中的一个文件时,差异变得明显。用忍者重建需要1.196s
单声道案例,但 999 文件案例只用了0.233s
。
注意:这些数字中不涉及显式头文件。
- .cpp和.h文件中的模板专用化声明
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 文本文件中的单词链表
- 如何使用Google Mock来模拟gettimeofday()
- CMake-按正确顺序将项目与C运行时对象文件链接
- 使用新行和不使用新行读取文件
- 在C++程序中输入的文本文件将不起作用,除非文本被复制和粘贴
- 挂起和取消挂起一个文件DLL
- 如何确定我已使用非编码文件到达 EOF?
- 命名空间中具有.h和.cpp文件的类
- 如何使用ndk-build.cmd构建Android.so文件
- 从包含m行的文件中提取n行,必要时(惰性地)重复该文件
- g++在多个文件上比使用谷歌模拟的单片单个文件慢得多
- C++:模拟餐饮公司计费程序-无法将错误数据输出到错误文件
- 在谷歌模拟中模拟文件写入过程
- 使用 GMOCK 模拟在 .cpp 中声明和定义的静态函数,而无需类文件
- 将 yyin 设置为"模拟"文件*?
- 如何模拟文件结束
- 在服务中模拟和加载用户配置文件后,环境变量是否受到影响
- 模拟一个文件指针c++