如何用c++模板减少编译时间?

时间:2022-09-02 09:20:35

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?

我正在将我的c++应用程序的一部分从使用旧的C类型数组更改为模板c++容器类。详情请参见这个问题。虽然解决方案工作得很好,但是我对模板代码的每一个微小的修改都会导致大量的重新编译,从而大大减慢了构建时间。是否有办法将模板代码从头中取出并返回到cpp文件中,从而使小的实现更改不会导致大的重新构建?

5 个解决方案

#1


14  

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.)

(与任何优化一样,在处理模板时,为编译器的速度进行优化可能不太值得,而不是首先寻找一种“算法”优化来大幅减少工作负载。)

#2


20  

Several approaches:

有几种方法:

  • The export keyword could theoretically help, but it was poorly supported and was officially removed in C++11.
  • 导出关键字理论上可以提供帮助,但它的支持很差,并且在c++ 11中被正式删除。
  • Explicit template instantiation (see here or here) is the most straightforward approach, if you can predict ahead of time which instantiations you'll need (and if you don't mind maintaining this list).
  • 显式模板实例化(请参阅此处或此处)是最直接的方法,如果您能够提前预测您将需要的实例化(如果您不介意维护这个列表)。
  • Extern templates, which are already supported by several compilers as extensions. It's my understanding that extern templates don't necessarily let you move the template definitions out of the header file, but they do make compiling and linking faster (by reducing the number of times that template code must be instantiated and linked).
  • Extern模板,它已被多个编译器作为扩展支持。我的理解是,extern模板并不一定允许您将模板定义移出头文件,但它们确实使编译和链接速度更快(通过减少必须实例化和链接模板代码的次数)。
  • Depending on your template design, you may be able to move most of its complexity into a .cpp file. The standard example is a type-safe vector template class that merely wraps a type-unsafe vector of void*; all of the complexity goes in the void* 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).
  • 根据您的模板设计,您可以将其大部分的复杂性转移到.cpp文件中。标准示例是一个类型安全的向量模板类,它仅仅包装一个类型不安全的void*向量;所有的复杂性都存在于位于.cpp文件中的void* vector中。Scott Meyers给出了一个更详细的例子,在有效的c++中(第42项,“明智地使用私有继承”,在第二版中)。

#3


5  

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:

让我们举一个例子:

// "bigArray/bigArray.hpp"

template <class T, class Allocator>
class BigArray
{
public:
  size_t size() const;

  T& operator[](size_t index);
  T const& operator[](size_t index) const;

  T& at(size_t index);
  T const& at(size_t index);

private:
  // impl
};

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:

这让你震惊吗?可能不会。毕竟,这看起来很简单。事实是,它不是。at方法可以在不丢失通用性的情况下进行分解:

// "bigArray/at.hpp"

template <class Container>
typename Container::reference_type at(Container& container,
                                      typename Container::size_type index)
{
  if (index >= container.size()) throw std::out_of_range();
  return container[index];
}

template <class Container>
typename Container::const_reference_type at(Container const& container,
                                            typename Container::size_type index)
{
  if (index >= container.size()) throw std::out_of_range();
  return container[index];
}

Okay, this changes the invocation slightly:

好的,这稍微改变了调用:

// From
myArray.at(i).method();

// To
at(myArray,i).method();

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.

但是,由于Koenig的查找,只要将它们放在同一个名称空间中,就可以将它们称为不合格,所以这只是一个习惯问题。

The example is contrived but the general point stands. Note that because of its genericity at.hpp never had to include bigArray.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.

这个例子是人为的,但一般的观点是成立的。注意,因为它在。hpp从不需要包括bigArray。hpp和仍然会生成紧密的代码,就像它是一个成员方法一样,只要我们愿意,我们可以在其他容器上调用它。

And now, a user of BigArray does not need to include at.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 alter std::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.

现在,BigArray的用户不需要包含at。如果她不使用的话……因此,如果您更改该文件中的代码,就可以减少她的依赖关系,并且不会受到影响:例如,alter std::out_of_range调用,以描述文件名和行号、容器的地址、容器的大小和我们试图访问的索引。

The other (not so obvious) advantage, is that if ever integrity constraint of BigArray is violated, then at is obviously out of cause since it cannot mess with the internals of the class, thus reducing the number of suspects.

另一个(不是很明显)的优点是,如果对BigArray的完整性约束被违反,那么显然是因为它不能扰乱类的内部结构,从而减少了嫌疑犯的数量。

This is recommended by many authors, such as Herb Sutters in C++ Coding Standards:

这是许多作者推荐的,例如c++编码标准中的草本Sutters:

Item 44: Prefer writing nonmember nonfriend functions

项目44:喜欢编写非成员非好友函数

and has been extensively used in Boost... But you do have to change your coding habits!

在Boost中被广泛使用……但是你必须改变你的编码习惯!

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++代码分析器,报告中包含但未使用的头文件可以帮助您理解这一点。

#4


4  

  • You could get a compiler that supports the export keyword, but that's not very likely to last.

    您可以得到一个支持export关键字的编译器,但这种情况不太可能持续下去。

  • You can use explicit instantiation, but unfortunately, that requires you to predict the template types you'll use ahead of time.

    您可以使用显式实例化,但不幸的是,这需要您提前预测将要使用的模板类型。

  • If you can factor out the templated types from your algorithm, you can put it in its own .cc file.

    如果你能从你的算法中提取出模板类型,你可以把它放到它自己的.cc文件中。

  • 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.

    我不建议这样做,除非这是一个主要的问题,但是:您可能能够提供一个模板容器接口,该接口通过对void*实现的调用实现,您可以随意更改。

#5


2  

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.

您可以在没有模板的情况下定义基类,并将大部分实现移动到那里。然后模板数组将只定义代理方法,这些方法使用基类来处理所有的事情。

#1


14  

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.)

(与任何优化一样,在处理模板时,为编译器的速度进行优化可能不太值得,而不是首先寻找一种“算法”优化来大幅减少工作负载。)

#2


20  

Several approaches:

有几种方法:

  • The export keyword could theoretically help, but it was poorly supported and was officially removed in C++11.
  • 导出关键字理论上可以提供帮助,但它的支持很差,并且在c++ 11中被正式删除。
  • Explicit template instantiation (see here or here) is the most straightforward approach, if you can predict ahead of time which instantiations you'll need (and if you don't mind maintaining this list).
  • 显式模板实例化(请参阅此处或此处)是最直接的方法,如果您能够提前预测您将需要的实例化(如果您不介意维护这个列表)。
  • Extern templates, which are already supported by several compilers as extensions. It's my understanding that extern templates don't necessarily let you move the template definitions out of the header file, but they do make compiling and linking faster (by reducing the number of times that template code must be instantiated and linked).
  • Extern模板,它已被多个编译器作为扩展支持。我的理解是,extern模板并不一定允许您将模板定义移出头文件,但它们确实使编译和链接速度更快(通过减少必须实例化和链接模板代码的次数)。
  • Depending on your template design, you may be able to move most of its complexity into a .cpp file. The standard example is a type-safe vector template class that merely wraps a type-unsafe vector of void*; all of the complexity goes in the void* 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).
  • 根据您的模板设计,您可以将其大部分的复杂性转移到.cpp文件中。标准示例是一个类型安全的向量模板类,它仅仅包装一个类型不安全的void*向量;所有的复杂性都存在于位于.cpp文件中的void* vector中。Scott Meyers给出了一个更详细的例子,在有效的c++中(第42项,“明智地使用私有继承”,在第二版中)。

#3


5  

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:

让我们举一个例子:

// "bigArray/bigArray.hpp"

template <class T, class Allocator>
class BigArray
{
public:
  size_t size() const;

  T& operator[](size_t index);
  T const& operator[](size_t index) const;

  T& at(size_t index);
  T const& at(size_t index);

private:
  // impl
};

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:

这让你震惊吗?可能不会。毕竟,这看起来很简单。事实是,它不是。at方法可以在不丢失通用性的情况下进行分解:

// "bigArray/at.hpp"

template <class Container>
typename Container::reference_type at(Container& container,
                                      typename Container::size_type index)
{
  if (index >= container.size()) throw std::out_of_range();
  return container[index];
}

template <class Container>
typename Container::const_reference_type at(Container const& container,
                                            typename Container::size_type index)
{
  if (index >= container.size()) throw std::out_of_range();
  return container[index];
}

Okay, this changes the invocation slightly:

好的,这稍微改变了调用:

// From
myArray.at(i).method();

// To
at(myArray,i).method();

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.

但是,由于Koenig的查找,只要将它们放在同一个名称空间中,就可以将它们称为不合格,所以这只是一个习惯问题。

The example is contrived but the general point stands. Note that because of its genericity at.hpp never had to include bigArray.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.

这个例子是人为的,但一般的观点是成立的。注意,因为它在。hpp从不需要包括bigArray。hpp和仍然会生成紧密的代码,就像它是一个成员方法一样,只要我们愿意,我们可以在其他容器上调用它。

And now, a user of BigArray does not need to include at.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 alter std::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.

现在,BigArray的用户不需要包含at。如果她不使用的话……因此,如果您更改该文件中的代码,就可以减少她的依赖关系,并且不会受到影响:例如,alter std::out_of_range调用,以描述文件名和行号、容器的地址、容器的大小和我们试图访问的索引。

The other (not so obvious) advantage, is that if ever integrity constraint of BigArray is violated, then at is obviously out of cause since it cannot mess with the internals of the class, thus reducing the number of suspects.

另一个(不是很明显)的优点是,如果对BigArray的完整性约束被违反,那么显然是因为它不能扰乱类的内部结构,从而减少了嫌疑犯的数量。

This is recommended by many authors, such as Herb Sutters in C++ Coding Standards:

这是许多作者推荐的,例如c++编码标准中的草本Sutters:

Item 44: Prefer writing nonmember nonfriend functions

项目44:喜欢编写非成员非好友函数

and has been extensively used in Boost... But you do have to change your coding habits!

在Boost中被广泛使用……但是你必须改变你的编码习惯!

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++代码分析器,报告中包含但未使用的头文件可以帮助您理解这一点。

#4


4  

  • You could get a compiler that supports the export keyword, but that's not very likely to last.

    您可以得到一个支持export关键字的编译器,但这种情况不太可能持续下去。

  • You can use explicit instantiation, but unfortunately, that requires you to predict the template types you'll use ahead of time.

    您可以使用显式实例化,但不幸的是,这需要您提前预测将要使用的模板类型。

  • If you can factor out the templated types from your algorithm, you can put it in its own .cc file.

    如果你能从你的算法中提取出模板类型,你可以把它放到它自己的.cc文件中。

  • 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.

    我不建议这样做,除非这是一个主要的问题,但是:您可能能够提供一个模板容器接口,该接口通过对void*实现的调用实现,您可以随意更改。

#5


2  

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.

您可以在没有模板的情况下定义基类,并将大部分实现移动到那里。然后模板数组将只定义代理方法,这些方法使用基类来处理所有的事情。