如何减少 C++ 的编译时间模板
我正在将我的 C++ 应用程序的一部分从使用旧的 C 类型数组更改为模板化的 C++ 容器类。请参阅此问题了解详细信息。虽然该解决方案运行良好,但我对模板化代码所做的每一个微小更改都会导致发生大量重新编译,从而大大减慢构建时间。有没有什么方法可以将模板代码从标头中取出并返回到 cpp 文件中,以便较小的实现更改不会导致重大重建?
I'm in the process of changing part of my C++ app from using an older C type array to a templated C++ container class. See this question for details. While the solution is working very well, each minor change I make to the templated code causes a very large amount of recompilation to take place, and hence drastically slows build time. Is there any way of getting template code out of the header and back into a cpp file, so that minor implementation changes don't cause major rebuilds?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
几种方法:
void*
类型不安全向量;所有的复杂性都集中在 .cpp 文件中的void*
向量中。 Scott Meyers 在 Effective C++ 中给出了更详细的示例(第 42 项,“明智地使用私有继承”,第二版)。Several approaches:
void*
; all of the complexity goes in thevoid*
vector that resides in a .cpp file. Scott Meyers gives a more detailed example in Effective C++ (item 42, "Use private inheritance judiciously", in the 2nd edition).我认为一般规则适用。尝试减少代码各部分之间的耦合。将太大的模板头分解为一起使用的较小的函数组,因此不必将整个内容包含在每个源文件中。
另外,尝试使标头快速进入稳定状态,也许可以针对较小的测试程序对它们进行测试,这样当集成到较大的程序中时,它们就不需要(太多)更改。
(与任何优化一样,在处理模板时优化编译器的速度可能不太值得,而不是首先找到一种可以大幅减少工作负载的“算法”优化。)
I think the general rules apply. Try to reduce coupling between parts of the code. Break up too large template headers into smaller groups of functions used together, so the whole thing won't have to be included in each and every source file.
Also, try to get the headers into a stable state fast, perhaps testing them out against a smaller test program, so they wouldn't need changing (too much) when integrated into a larger program.
(As with any optimization, it might be less worth to optimize for the compiler's speed when dealing with templates, rather than finding an "algorithmic" optimization that reduces the work-load drastically in the first place.)
使用模板作为解决问题的技术可能会导致编译速度减慢。一个经典的例子是 C 语言中的 std::sort 与 qsort 函数。该函数的 C++ 版本需要更长的时间来编译,因为它需要在每个翻译单元中进行解析,而且几乎每次使用该函数都会创建一个不同的实例该模板的(假设闭包类型通常作为排序谓词提供)。
尽管这些速度下降是可以预料的,但有一些规则可以帮助您编写高效的模板。下面描述其中的四个。
Chiel 规则
下面介绍的 Chiel 规则描述了哪些 C++ 结构对于编译器来说是最困难的。如果可能,最好避免这些构造以减少编译时间。
以下 C++ 功能/结构按编译时间降序排序:
基于上述规则的优化在设计和开发 Boost.TMP 时使用。尽可能避免使用顶级构造来快速编译模板。
下面是一些示例,说明如何利用上面列出的规则。
减少模板实例化
让我们看一下 std::conditional。它的声明是:
每当我们更改提供给该模板的三个参数中的任何一个时,编译器都必须创建它的一个新实例。例如,想象以下类型:
现在,以下所有类型都会以不同类型的实例化结束:
我们可以通过将条件的实现更改为来减少实例化的数量:
在这种情况下,编译器将仅创建类型的两个实例化所有可能的论点都是“有条件的”。有关此示例的更多详细信息,请查看Odin Holmes 关于 Kvasir 库的演讲。
创建显式模板实例化
每当您怀疑某个模板实例将被经常使用时,显式实例化它是一个好主意。通常,
std::string
是std::basic_string
的显式实例化。为编译时算法创建专业化
Kvasir-MPL 专门针对长类型列表的算法以加快速度。您可以在此处查看示例< /a>.在此头文件中,排序算法是手动专门针对 255 种类型的列表。手动专门化可以加快长列表的编译速度。
Using templates as a problem solving technique can create compilation slowdowns. A classical example of this is the std::sort vs. qsort function from C. The C++ version of this function takes longer to compile because it needs to be parsed in every translation unit and because almost every use of this function creates a different instance of this template (assuming that closure types are usually provided as sorting predicate).
Although these slowdowns are to be expected, there are some rules that can help you to write efficient templates. Four of them are described below.
The Rule of Chiel
The Rule of Chiel, presented below, describes which C++ constructs are the most difficult ones for the compiler. If possible, it’s best to avoid those constructs to reduce compilation times.
The following C++ features/constructs are sorted in descending order by compile time:
Optimizations based on the above rules were used when Boost.TMP was designed and developed. As much as possible, avoid top constructs for quick template compilation.
Below are some examples illustrating how to make use of the rules listed above.
Reduce Template Instantiations
Let's have a look at std::conditional. Its declaration is:
Whenever we change any of three arguments given to that template, the compiler will have to create a new instance of it. For example, imagine the following types:
Now, all the following will end up in instantiations of different types:
We can reduce the number of instantiations by changing the implementation of conditional to:
In this case, the compiler will create only two instantiations of type “conditional” for all possible arguments. For more details about this example, check out Odin Holmes' talk about the Kvasir library.
Create Explicit Template Instantiations
Whenever you suspect that an instance of a template is going to be used often, it’s a good idea to explicitly instantiate it. Usually,
std::string
is an explicit instantiation ofstd::basic_string<char>
.Create Specializations for Compile-time Algorithms
Kvasir-MPL specializes algorithms for long lists of types to speed them up. You can see an example of this here. In this header file, the sorting algorithm is manually specialized for a list of 255 types. Manual specialization speeds up compilations for long lists.
首先,为了完整起见,我将介绍简单的解决方案:仅在必要时使用模板化代码,并将其基于非模板代码(在其自己的源文件中实现)。
然而,我怀疑真正的问题是您使用泛型编程就像使用典型的面向对象编程一样,最终会得到一个臃肿的类。
举个例子:
这让你震惊吗?可能不会。毕竟它看起来非常简约。问题是,事实并非如此。
at
方法可以被分解出来,而不失任何通用性:好的,这稍微改变了调用:
但是,由于 Koenig 的查找,只要将它们放在同一个命名空间中,您就可以将它们称为非限定的,所以这只是一个习惯问题。
这个例子是人为的,但总体观点是成立的。请注意,由于其通用性,
at.hpp
永远不必包含bigArray.hpp
,并且仍然会生成像成员方法一样紧凑的代码,只是我们可以如果我们愿意,可以在其他容器上调用它。现在,
BigArray
的用户如果不使用at.hpp
,则不需要包含它...从而减少了她的依赖性,并且如果您更改了则不会受到影响该文件中的代码:例如 alterstd::out_of_range
调用以显示文件名和行号、容器的地址、其大小以及我们尝试访问的索引。另一个(不是那么明显)的优点是,如果 BigArray 的完整性约束被违反,那么 at 显然是不合理的,因为它不会扰乱 BigArray 的内部结构。类,从而减少嫌疑人的数量。
许多作者都推荐这样做,例如 C++ 编码标准 中的 Herb Sutters:
,并已在 Boost 中广泛使用...但你必须改变你的编码习惯!
当然,您只需要包含您所依赖的内容,应该有静态 C++ 代码分析器来报告包含但未使用的头文件,这可以帮助解决这个问题。
First of all, for completeness, I'll cover the straightforward solution: only use templated code when necessary, and base it on non-template code (with implementation in its own source file).
However, I suspect that the real issue is that you use generic programming as you would use typical OO-programming and end up with a bloated class.
Let's take an example:
Does this shock you ? Probably not. It seems pretty minimalist after all. The thing is, it's not. The
at
methods can be factored out without any loss of generality:Okay, this changes the invocation slightly:
However, thanks to Koenig's lookup, you can call them unqualified as long as you put them in the same namespace, so it's just a matter of habit.
The example is contrived but the general point stands. Note that because of its genericity
at.hpp
never had to includebigArray.hpp
and will still produce as tight code as if it were a member method, it's just that we can invoke it on other containers if we wish.And now, a user of
BigArray
does not need to includeat.hpp
if she does not uses it... thus reducing her dependencies and not being impacted if you change the code in that file: for example alterstd::out_of_range
call to feature the file name and line number, the address of the container, its size and the index we tried to access.The other (not so obvious) advantage, is that if ever integrity constraint of
BigArray
is violated, thenat
is obviously out of cause since it cannot mess with the internals of the class, thus reducing the number of suspects.This is recommended by many authors, such as Herb Sutters in C++ Coding Standards:
and has been extensively used in Boost... But you do have to change your coding habits!
Then of course you need to only include what you do depend on, there ought to be static C++ code analyzers that report included but unused header files which can help figuring this out.
您可以使用显式实例化;但是,只有您实例化的模板类型才会提前编译。
您也许能够利用 c++20 的模块 .
如果您可以从算法中提取模板化类型,则可以将其放入自己的 .cc 文件中。
我不会建议这样做,除非这是一个主要问题,但是:您也许能够提供一个模板容器接口,该接口是通过调用
void*
实现来实现的,您可以在会的。在 c++11 之前,您可以使用支持导出 关键字。
You can use explicit instantiation; however, only the template types you instantiate will compile ahead of time.
You might be able to take advantage of c++20's modules.
If you can factor out the templated types from your algorithm, you can put it in its own .cc file.
I wouldn't suggest this unless it's a major problem but: you may be able to provide a template container interface that is implemented with calls to a
void*
implementation that you are free to change at will.Before c++11 you could use a compiler that supports the export keyword.
您可以定义一个没有模板的基类,并将大部分实现移至那里。然后,模板化数组将仅定义代理方法,该方法对所有内容都使用基类。
You can define a base class without templates and move most of the implementation there. The templated array would then define only proxy methods, that use base class for everything.