要添加到预编译头文件的内容

时间:2014-05-28 06:35:32

标签: c++ compilation include precompiled-headers

我是预编译标头的新手,我只是想知道要包含什么。我们的项目有大约200个源文件。

那么,我是否真的包含了每个第三方库?

如果我在三个源文件中使用地图,我会添加吗?如果我使用它,我会添加它吗?我是否需要删除旧的直接包含或ifdef和pragma一旦指令仍然有效?

您是否有任何第三方图书馆无法添加?

那么预编译的头部是否会变得庞大?

同样,即使是在预编译的表单中,也不会有突然出现所有这些标题的开销吗?

修改

我在clang找到了一些信息:

预编译的头实现在以下情况下提高了性能:

  • 加载PCH文件要比重新解析存储在PCH文件中的标头包快得多。因此,预编译的头部设计试图最小化读取PCH文件的成本。理想情况下,此成本不应随预编译头文件的大小而变化。
  • 最初生成PCH文件的成本不是很大,以至于它无需首先解析捆绑的头文件,因此它可以抵消每个源文件的性能提升。这在多核系统上尤为重要,因为当所有编译都要求PCH文件是最新的时,PCH文件生成会对构建进行序列化。

Clang的预编译头文件采用紧凑的磁盘表示,可最大限度地减少PCH创建时间和初始加载PCH文件所需的时间。 PCH文件本身包含Clang的抽象语法树和支持数据结构的序列化表示,使用与LLVM的bitcode文件格式相同的压缩比特流存储。

Clang的预编译头文件是从磁盘“懒洋洋地”加载的。当最初加载PCH文件时,Clang只从PCH文件中读取少量数据,以确定存储某些重要数据结构的位置。在此初始加载中读取的数据量与PCH文件的大小无关,因此较大的PCH文件不会导致更长的PCH加载时间。 PCH文件中的实际标题数据 - 宏,函数,变量,类型等 - 仅在从用户代码引用时才加载,此时只对该实体(及其依赖的实体)进行反序列化来自PCH文件。使用这种方法,为翻译单元使用预编译头的成本与从头中实际使用的代码量成比例,而不是与头本身的大小成比例。

对我而言,这似乎表明至少是clang:

  • 注意使预编译头的加载时间与大小无关。
  • 预编译头的使用次数与预编译头大小无关,并且与使用的信息量成比例
  • 与目前为止给出的答案相反,这似乎表明,即使只包含一个外部文件(比如<map>),也值得将它包含在预编译的头文件中(仍然会加速重新编译那个源文件)

必须有某种地图来映射所有信息。这张地图可能会变大,但也许这不重要?不确定我是否正确,或者它是否适用于所有编译器...

2 个答案:

答案 0 :(得分:7)

对此没有100%的确切答案,因为它取决于您的项目。最好的办法是亲自尝试,看看会发生什么。

然而,

&#34;那么,我是否真的包含了每个第三方库? &#34;

不,基本上你包括标题,其中:

  • 您的来源经常使用。 &#34;通常&#34;但是并没有明确定义,但是让我们说它超过了10%的源文件。 (然而,我随机选择了这个数字。也许它应该更大。)
  • 大部分时间都没有更改(因为单个标题的更改意味着您需要重新编译所有源代码)。第三方库不会被更改,因此它们是最佳候选者,但如果您确定它们很少会被更改或在特殊情况下,您也可以使用自己项目中的标题。< / LI>

但不要&#34;只是&#34;包括库的所有标头。包括您正在使用的内容。

&#34;如果我在三个源文件中使用地图,我会添加吗?&#34;

见上文。对此没有明确的答案,但我个人认为三个源文件太少了。

&#34;如果我使用它,我会添加它吗?&#34;

(我理解这个问题,因为它将是&#34;如果我在单一来源中使用头文件并将其添加到预编译的头文件中该怎么办?&#34;

什么都不会破坏你的申请。但它会:

  • 预编译标头的编译时间更长。
  • 预编译头文件更大。
  • 较慢地编译源文件。

如果标题具有平均大小,则在单个标题的情况下它是完全无关紧要的。但是,如果添加数百个此类标头,则会降低整个编译速度。

&#34;我是否需要删除旧的直接包含或ifdef和pragma一旦指令仍然有效?&#34;

你可能可以做这样的事情,但我强烈建议不要这样做。但是,您并非必需

您可以想象预编译的标题只是在所有源的开头包含标题。例如:

precompiled.h

#include <iostream>
#include <string>

MyClass.cpp

#include "MyClass.h"

MyClass::MyClass()
{
// etc.

现在让我们假设您启用了预编译的标头。对于源文件,它与您写的相同:

#include "precompiled.h"
#include "MyClass.h"

MyClass::MyClass()
{
// etc. 
你能正常做到吗?是的你可以!预编译的标题就像那样(但速度更快),这意味着:

  • 是的,保留了宏。在所有源文件中定义了预编译头文件中定义的内容,这意味着:
    • 卫兵正常工作。
    • 如果存在检测OS的库,则所有宏仍然是定义且可用的。
    • 如果定义了其他宏(例如MIN(尽管现在建议使用std :: min)),您仍然可以正常使用它。
  • 我不知道pragma,但我相信它也能正常工作。

关于删除来源:如上所述,我高度反对。原因很简单:如果您将来需要关闭预编译的头文件怎么办? (事实上​​,我个人不时会关闭预编译的标题,看看我的代码是否仍然编译。我个人的理由是,如果你发布代码,一些用户将不会使用你的项目/ make文件,但他们会创建自己的项目(例如,如果他们使用不同的IDE,如Code :: Blocks或QtCreator),所以我尝试以这样的方式制作我的项目,你只需要添加源文件,配置正确的包含路径,链接正确的库,它应该编译。)

&#34;您是否有任何第三方图书馆无法添加?&#34;

我无法想到任何......

另一方面,我可以想到一些最好添加的恕我直言(如果你使用它们) - 例如提升。它在大多数情况下使用模板 - 根据我的个人经验,模板最大程度地减慢了编译速度,因为您需要不包括onlu声明,还需要包含定义。恕我直言,这是C ++模板的最大弱点。

&#34;预编译的标头然后变得庞大吗? &#34;

他们可以。这就是为什么你需要找到最好的头文件子集(而不是盲目地包括所有内容)来获得最佳结果的原因。我的大约50MB,但仍然加快编译速度。 (整个分钟,因为我使用了很多模板。)

&#34;因为,即使是在预编译的表单中,也不会突然出现所有这些标题的开销?&#34;

如果使用预编译头,则准备一些头文件并将其包含在所有源文件中。这意味着,从单个源文件的角度来看,您包含了源文件不需要的一些头文件。 是开销。但是,包含预编译头文件要快得多,因此如果包含一些不需要的头文件,它仍然会更快。但是当你超过某个限制时(让我们说如果超过90%的来源超过90%包含标题是不需要的话),当使用预编译的标题时,开始减慢编译速度。这就是为什么您需要包含主要使用的标头,并避免包含仅包含在少数源文件中(或根本不使用)的标头。

通常,使用预编译的头文件会增加磁盘上所需的空间(在这些时候,绝对不显着)和RAM中需要的空间(同样,这些时间并不重要)。它是以内存为代价获得更高速度的完美例子。


最后的建议很简单:亲自尝试一下。检查添加标题时会发生什么,当您觉得编译速度很慢时,请检查是否包含了大部分未使用的内容。

答案 1 :(得分:2)

正如您所知,编译C / C ++源代码是一项非常耗时的任务,其中一个原因是编译器需要编译您直接和间接包含在源代码中的每一块代码。大多数情况下,冗余是因为大多数包含的文件都是不会随时间变化的库。为了缓解这个问题,引入了预编译头的概念。通过预编译头文件,可以告诉编译器一组包含文件不太可能随时间改变,这样,编译器可以通过编译指定文件一次并保存结果来优化编译过程。然后,只要编译器需要编译项目,它就会跳过指定源的编译并重用这些保存的编译文件。

因此,最好包含不受预编译头文件频繁更改的文件。当然,您可以添加经常更改的代码,但这会忽略使用预编译头的全部内容。

顺便说一下,不要担心预编译的标头会变得庞大。该概念主要用于减少大型项目的编译时间,其中通常有大量的第三方库。在这些情况下,这些文件通常应该并且将会变得庞大。

另见Wikipedia's entry on Precompiled Headers