浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第9章 在实践中使用模板:9.3 预编译头文件

Posted on 2020-05-22 21:08  浅墨浓香  阅读(458)  评论(1编辑  收藏  举报

9.3 Precompiled Headers

9.3 预编译头文件

 

Even without templates, C++ header files can become very large and therefore take a long time to compile. Templates add to this tendency, and the outcry of waiting programmers has in many cases driven vendors to implement a scheme usually known as precompiled headers (PCH). This scheme operates outside the scope of the standard and relies on vendor- specific options. Although we leave the details on how to create and use precompiled header files to the documentation of the various C++ compilation systems that have this feature, it is useful to gain some understanding of how it works.

即使不使用模板,C++头文件也可能变得非常庞大,从而需要很长的编译时间。模板的使用更是加剧了这一问题,于是,程序员就呼吁编译器提供商实现一种预编译头文件的方案。该方案不是标准的要求,它主要依赖于编译器提供商的实现。虽然我们没有过多地讨论如何创建和使用预编译头文件(这部分内容需要参考提供该特性的C++编译系统的文档说明),但了解其运作机制总是有帮助的。

When a compiler translates a file, it does so starting from the beginning of the file and working through to the end. As it processes each token from the file (which may come from #included files), it adapts its internal state, including such things as adding entries to a table of symbols so they may be looked up later. While doing so, the compiler may also generate code in object files.

当翻译一个文件时,编译器是从文件的开头一直进行到文件末尾的。当编译器处理文件中的每一个符号(token)时(可能来自include的文件),会适配它的内部状态,例如在符号表中添加新的条目,以方便以后的查找。在这个过程中,编译器还会在目标文件中生成代码。

The precompiled header scheme relies on the fact that code can be organized in such a manner that many files start with the same lines of code. Let’s assume for the sake of argument that every file to be compiled starts with the same N lines of code. We could compile these N lines and save the complete state of the compiler at that point in a precompiled header. Then, for every file in our program, we could reload the saved state and start compilation at line N+1. At this point it is worthwhile to note that reloading the saved state is an operation that can be orders of magnitude faster than actually compiling the first N lines. However, saving the state in the first place is typically more expensive than just compiling the N lines. The increase in cost varies roughly from 20 to 200 percent.

预编译头文件方案依赖于下面的事实:在组织代码时,可以令许多文件都以相同的几行代码作为开始。为了方便讨论,我们假设每个要编译的文件都以相同的N行代码开始。我们可以编译这N行代码,并将编译器在这个点时的完整状态保存在预编译头文件中。然后,对于程序中每个文件,我们可以重新加载保存的状态并在第N+1行开始编译。此时值得注意的是,重载加载保存的状态是一个比实际编译前N行快几个数量级的操作。但是,第一次编译并保存这个状态要比编译这N行代码慢很多,编译时间代码可能延长20%-200%不等。

#include <iostream>

The key to making effective use of precompiled headers is to ensure that—as much as possible— files start with a maximum number of common lines of code. In practice this means the files must start with the same #include directives, which (as mentioned earlier) consume a substantial portion of our build time. Hence, it can be very advantageous to pay attention to the order in which headers are included. For example, the following two files:

利用预编译头文件提高编译速度的关键是:让尽可能多的文件,以可尽可能多的相同代码行开始。在实际应用中,这意味着文件必须以相同的#include提示符开始,(如前所述)这些include的文件消耗了我们大量的编译时间。因此,包含头文件的顺序是非常重要的。例如,以下两个文件:

#include <vector>
#include <list>

and

以及

#include <list>
#include <vector>

inhibit the use of precompiled headers because there is no common initial state in the sources.

就不能使用预编译头文件,因为两者在源文件中没有共同的初始状态(顺序不一致)

Some programmers decide that it is better to #include some extra unnecessary headers than to pass on an opportunity to accelerate the translation of a file using a precompiled header. This decision can considerably ease the management of the inclusion policy. For example, it is usually relatively straight forward to create a header file named std.hpp that includes all the standard headers:

有些程序员会认为,在使用预编译头文件时,允许include一部分额外的、非必要的头文件,要比只选择有用的头文件具有更好的编译速度。这还可以让包含策略的管理变得更加容易。例如,通常相对简单的方法是创建立一个名为std.hpp的头文件,其中包含所有标准头文件。

#include <iostream>
#include <string>
#include <vector>
#include <deque>
#include <list>

This file can then be precompiled, and every program file that makes use of the standard library can then simply be started as follows:

预编译这个文件,现在每个使用标准库的程序只需要这样开始就可以:

#include "std.hpp"

Normally this would take a while to compile, but given a system with sufficient memory, the pre-compiled header scheme allows it to be processed significantly faster than almost any single standard header would require without precompilation. The standard headers are particularly convenient in this way because they rarely change, and hence the precompiled header for our std.hpp file can be built once. Otherwise, precompiled headers are typically part of the dependency configuration of a project (e.g., they are updated as needed by the popular make tool or an integrated development environment’s (IDE) project build tool).

通常预编译这个文件需要一段时间,但是对于具有充足内存的系统,预编译头文件方案会使得处理速度比编译大多数单个(未经预编译的)标准头文件快很多。利用这种方式使用标准头文件是非常方便的,因为它们几乎很少变化。因此,我们的预编译头文件std.hpp就只需创建一次,就可以在后面多次使用。相反,如果不能保证这种稳定性,预编译头文件可能就会因为项目具体情况的变化而不断变化,并成为项目依赖性配置的一部分(例如,需要根据make工具或IDE编译工具的需要进行更新)。

One attractive approach to manage precompiled headers is to create layers of precompiled headers that go from the most widely used and stable headers (e.g., our std.hpp header) to headers that aren’t expected to change all the time and therefore are still worth precompiling. However, if headers are under heavy development, creating precompiled headers for them can take more time than what is saved by reusing them. A key concept to this approach is that a precompiled header for a more stable layer can be reused to improve the precompilation time of a less stable header. For example, suppose that in addition to our std.hpp header (which we have precompiled), we also define a core.hpp header that includes additional facilities that are specific to our project but nonetheless achieve a certain level of stability:

管理预编译头文件的一种可取方法是:对预编译头文件进行分层,从使用频率最高的、最稳定的头文件(如std.hpp) 到那些预期一直不会变化的(因此值得编译)头文件。但是,如果头文件正处于频繁的开发阶段,为它们创建预编译头文件所花费的时间,可能比重用这些预编译头文件所节省的时间还要多。因此,解决这个问题的关键之处在于:应该对那些更稳定的头文件进行预编译,然后在不太稳定的头文件中重用这个稳定的预编译头文件,从而提高整个编译效率。例如,除了前面己经预编译过的std.hpp头文件外,我们还定义了一个专为我们项目准备的、具有额外功能的core.hpp头文件,但它尚未达到稳定状态:

#include "std.hpp"
#include "core_data.hpp
#include "core_algos.hpp"

Because this file starts with #include "std.hpp", the compiler can load the associated precom-piled header and continue with the next line without recompiling all the standard headers. When the file is completely processed, a new precompiled header can be produced. Applications can then use #include "core.hpp" to provide access quickly to large amounts of functionality because the compiler can load the latter precompiled header.

因为该文件是以#include ”std.hpp”开头的,编译器会加载相头的预编译头文件,然后从下一行开始编译,而不会重新编译所有的标准头文件。当文件core.hpp完全经过处理之后,就会产生一个新的预编译头文件。于是,应用程序可以使用#include “core.hpp”来提供比(std.hpp)功能更多、速度更快的访问。因为编译器可以直接加载后面这个经过预编译的头文件。