预编译头文件需要添加什么?

8

我刚开始接触预编译头文件,不确定应该包含哪些内容。我们的项目有大约200个源文件。

那么,我是不是需要包含所有第三方库?

如果我在三个源文件中使用了一个映射表,我需要将其添加进去吗?如果我只在一个源文件中使用了它,我需要添加吗?我需要删除旧的直接包含吗?或者条件编译和 pragma once 指令仍然有效吗?

有没有第三方库你不会添加进去?

预编译头文件会变得非常庞大,对吗?

也就是说,突然之间在所有地方都包含这些头文件,即使是预编译形式,也会产生额外负担,对吗?

编辑:

我在clang上找到了一些信息:

预编译头文件实现可以提高性能的情况:

  • 加载PCH文件的速度比重新解析存储在PCH文件中的头文件束更快。因此,预编译头文件设计试图将读取PCH文件的成本最小化。理想情况下,这个成本不应该随着预编译头文件大小的变化而变化。
  • 生成PCH文件的成本不会太大,以至于它抵消了由于首先消除需要解析捆绑包头文件而导致的每个源文件的性能提升。这对于多核系统特别重要,因为PCH文件的生成会使所有编译序列化,需要更新PCH文件。

Clang的预编译头文件采用紧凑的磁盘表示方法,从而最小化了PCH创建时间和初始加载PCH文件所需的时间。PCH文件本身包含Clang抽象语法树和支持数据结构的序列化表示,使用与LLVM位代码文件格式相同的压缩比特流存储。

Clang的预编译头文件从磁盘“懒加载”。当首次加载PCH文件时,Clang仅读取一小部分数据来确定某些重要数据结构的存储位置。在此初始加载中读取的数据量独立于PCH文件的大小,因此更大的PCH文件不会导致更长的PCH加载时间。实际的头文件数据——宏、函数、变量、类型等——只有在从用户代码中引用它时才从PCH文件中加载,此时只有该实体(以及依赖于它的实体)会从PCH文件中反序列化。采用这种方法,使用预编译头文件进行翻译单元的成本与实际使用的头文件代码量成正比,而不是与头文件本身的大小成正比。

对我来说,这似乎表明至少在Clang上:

  • 已经注意到使预编译头文件的加载时间与大小无关。
  • 使用预编译头文件的时间与预编译头文件的大小无关,与使用的信息量成比例。
  • 与迄今为止给出的答案相反,这似乎表明即使将外部文件(例如<map>)包含在其中一次,也值得将其包含在预编译头文件中(仍然可以加速该源文件的重新编译)。

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


我主要将那些不会改变且通常需要的头文件放在其中,例如<string>或包含所有项目特定类型或静态变量的头文件。 - Zaiborg
关于包含 <map>:我仍然主张“如果您在单个文件中使用它,则不要这样做”。原因是:即使使用clang优化,符号初始表也会变得更大。如果您包含单个文件,则可能微不足道,但如果您包含数十个或数百个这样的文件呢?每次编译源文件时都必须加载表格。是的,与加载整个PCH相比,它的开销要小得多,但我不会指望这件事情微不足道。另一个原因是PCH文件可能会被操作系统缓存为优化。如果文件变得更大,则可能超过... - user1476710
尽管这似乎不太可能,但是缓存仍然会被使用。当然,我的两个论点都没有得到衡量,这可能是“过早优化”的情况,但是,如果添加<map>没有任何收益(如果您不添加它并将其包含在单个源文件中,则仍必须编译一次),为什么要使PCH文件变得更大呢? - user1476710
@Cookie 是的,你说得对,我道歉。我上次评论的第二部分是错误的 - 我最终意识到了你的观点和速度优势。 - user1476710
@Cookie 关于最佳实践 - 我很抱歉,但在这方面,我无法提供任何新的建议:尝试它。测量它。亲自体验一下。问题是最优的标头集取决于您的项目。即使您有一个工具可以计算最佳的标头子集,每次添加/删除文件,更改包含甚至更改源文件时,它也会发生变化。 - user1476710
显示剩余7条评论
2个回答

8

这个问题没有100%准确的答案,因为它取决于你的项目。最好的方法是自己尝试并看看会发生什么。

然而,

"那么,我是真的需要包含每个第三方库吗?"

不用,基本上你只需要包含头文件:

  • 经常被你的源代码使用。但是,“经常”没有很明确定义,但我们可以说它被超过10%的源文件使用(我随机选择了这个数字,也许应该更大)。
  • 大部分时间不会改变(因为单个头文件的更改意味着您需要重新编译所有源文件)。不希望第三方库被更改,所以它们是最佳选择,但是如果您确定它们很少更改或在特殊情况下更改,则也可以使用来自您自己项目的头文件。

但是不要“只是”包含一个库的所有头文件。只包含你正在使用的头文件。

"如果我在三个源文件中使用映射,我需要添加它吗?"

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

"如果我只在一个源文件中使用它,我需要添加它吗?"

(我理解这个问题是指“如果我在单个源文件中使用头文件并将其添加到预编译头文件中怎么办?”)

这不会破坏你的应用程序。但是它会使:

  • 预编译头文件的编译时间更长。
  • 预编译头文件更大。
  • 源文件的编译速度变慢。

如果只有一个头文件,那么它完全可以忽略不计,如果头文件的大小是平均大小。但是,如果您添加了数百个这样的头文件,则会减慢整个编译过程。

"我需要删除旧的直接包含吗,还是# ifdef和#pragma once指令仍然有效?"

你可能可以这样做,但我强烈建议不要这样做。但是,您不必这样做。

您可以想象预编译头文件只是在所有源代码之前包含头文件的例子:

precompiled.h

#include <iostream>
#include <string>

MyClass.cpp

#include "MyClass.h"

MyClass::MyClass()
{
// etc.

现在假设你启用了预编译头。对于源文件来说,就好像你写了以下内容:
#include "precompiled.h"
#include "MyClass.h"

MyClass::MyClass()
{
// etc. 

你能正常地做到这一点吗?是的,你可以! 预编译头就像这样(但更快),这意味着:

  • 是的,宏被保留。预编译头中定义的任何内容都在所有源文件中定义,这意味着:
    • 守卫正常工作。
    • 如果有一个库检测操作系统,所有宏仍然被定义并可用。
    • 如果有其他宏定义(例如MIN(尽管现在建议使用std :: min),您仍然可以正常使用它。
  • 我不知道pragma,但我相信它也可以正常工作。

关于从源代码中删除包含的内容:如上所述,我强烈反对这样做。原因很简单:如果将来需要关闭预编译头怎么办?(事实上,我个人会时不时地关闭预编译头以查看我的代码是否仍然可以编译。我的个人原因是,如果发布代码,一些用户将不使用您的项目/制作文件,而是创建自己的项目(例如,如果他们使用不同的IDE,如Code :: Blocks或QtCreator),因此我试图以这种方式制作我的项目,使得您只需要添加源文件,配置正确的包含路径,链接正确的库即可编译。)

“有任何第三方库您不会添加吗?”

我想不出任何...

另一方面,我可以想到一些最好添加的IMHO(如果您使用它们)-例如boost。它大多数时候使用模板-根据我的个人经验,模板是编译速度最慢的,因为您需要包含不仅是声明,而且还有定义。 IMHO这是C ++模板的最大弱点。

“预编译头难道不会变得非常庞大吗?”

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

“也就是说,突然之间在所有地方都包含这些头文件,即使是以预编译形式,也会有开销吗?”

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

一般来说,使用预编译头文件会增加磁盘占用空间(在现在这个时代,绝对微不足道),以及内存占用空间(同样,在现在这个时代并不重要)。这是“以内存为代价获取速度”的完美例子。


最后的建议很简单:自己尝试一下。当您添加头文件时,查看会发生什么情况,当您感觉编译速度变慢时,请检查是否包含了大量未被使用的内容。


2
正如你所知,编译C/C++源代码是一个耗时的任务,其中一个原因是编译器需要编译你直接和间接包含到源代码中的每一块代码,而在大多数情况下,这是一种冗余,因为大多数被包含的文件都是不会随时间改变的库文件。为了缓解这个问题,引入了预编译头文件的概念。通过预编译头文件,可以告诉编译器一组包含文件很可能不会随时间改变,这样,编译器就可以通过一次编译指定的文件并保存结果来优化编译过程。然后,每当编译器需要编译该项目时,它会跳过指定源文件的编译并重用这些已保存的编译文件。
因此,在预编译头文件中包含不经常更改的文件是一个好主意。当然,您也可以在其中添加经常更改的代码,但这将忽略使用预编译头文件的整个目的。
顺便说一下,不要担心预编译头文件会变得非常庞大。这个概念主要是为了减少大型项目的编译时间,在这些项目中通常有大量的第三方库。在这些情况下,这些文件通常应该并且会变得非常庞大。
另请参阅维基百科关于预编译头文件的条目

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接