与使用Google模拟的单个文件相比,g ++在多个文件上速度要慢得多

时间:2019-02-14 19:53:07

标签: c++ gcc

我遇到的问题似乎与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();
}

然后我复制了它,删除了main并替换了递增的数字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

由于我打算扩展到数百个文件,因此要复杂得多,因此减少构建时间很重要。谁能帮忙解释为什么会这样,或者提供较小的解决方法?我认为这部分与预处理器和包含保护措施所带来的节省有关,因为即使我只包含一个文件,时间差也会大大增加(在一种情况下为5倍),但不包括时,仍然存在,整体文件的处理速度提高了20倍。

g ++的版本为4.4.2,但我检查了最新版本8.2.0,并且该版本也存在。

2 个答案:

答案 0 :(得分:4)

有两种不同的效果:

  1. 编译器调用开销:编译器是复杂的可执行文件,有时甚至被分成前端和后端可执行文件,并且即使所有源文件都传递给同一个编译器,前端也会产生每个单独源文件的后端调用前端。 Gcc和llvm就是这样。

    • 指定g++ -v以查看这些冗余的编译器调用。这回答了您想到的主要问题,我想为什么即使没有头文件也会发生这种情况。
  2. 开销是由于从头开始一遍又一遍地解析和编译相同的头文件。在实际示例中,此标头开销比编译器调用本身要重要得多。

  

因为即使我只包含一个文件,时间差也会大大增加(在一种情况下为五分之一)

是的!而且这也可能慢1000倍,而不是5倍。使用模板密集型代码,编译器需要在编译时做很多事情。

尤其是对于C ++代码,在拆分多个源文件时,速度变慢会给您带来打击,因为C ++是 header密集型。您的所有源代码*.cpp都是单独编译的,并且它们包含的所有标头对于每个单独的源文件都是多余的。

现在,如果将所有源文件归为一类,则所有头文件(如您所说)由于包含保护而仅被解析一次。由于编译器将大量时间用于解析和编译标头,因此这非常重要,尤其是对于模板繁重的代码(例如使用STL就足够了)。

手写C ++源代码和生成的C ++源代码的源文件数是一个折衷方案:

  1. 我的完整重建时间很快,但是我的增量构建时间很慢。

    • 只有一个源文件(即* .cpp文件)或很少的源文件时就是这种情况。
  2. 我的完整构建时间很慢,但是增量构建时间却很快。

    • 当您有许多小源文件(即* .cpp文件)时,就是这种情况。

(在任何情况下,头文件的数量都无关紧要(除非您总是拉入过多的冗余内容。这大约是编译器调用的数量,即* .cpp或* .o文件的数量) )

对于1.,从头开始的完整编译时间很短,因为编译器只看到一次所有标头,这在C ++中尤其重要,尤其是在基于模板的仅标头(或标头密集型)的库(如STL或boost)中。

对于2.,单独的编译时间很快,因为仅更改数百个文件中的一个时,*。cpp文件中的代码很少。

这在很大程度上取决于您的用例。

如果生成C ++代码,则应在生成器中添加一个选项,以允许用户选择采用这种折衷方法。

答案 1 :(得分:2)

我相信在这种情况下,大部分开销来自打开和关闭文件。在这两种情况下,您都有一个过程来完成这项工作。

我与cat程序进行了比较,该程序将结果转储到/ dev / null。 cat test_*.cpp >/dev/null用了约0.008s,cat mon.cpp >/dev/null用了0.001s。 999个文件几乎相差10倍。除此之外,编译器还必须为其编译的每个文件设置一些内部管理,对于大型的整体情况,只需要执行一次即可。

但是,正如其他人回答的那样,在诸如make或ninja之类的构建系统中设置该构建时,与单个文件相比,仅触摸多个文件中的一个/ em>差异就显而易见。对于单声道,使用忍者进行重建花费了1.196s,对于999档案来说,仅花费了0.233s

注意:这些编号中没有明确的头文件。