如何使用C ++模板减少编译时间

时间:2010-05-13 14:09:18

标签: c++ templates compile-time

我正在将部分C ++应用程序从使用旧的C类型数组更改为模板化C ++容器类。有关详细信息,请参阅this question。虽然解决方案运行良好,但我对模板化代码所做的每一个小改动都会导致进行大量的重新编译,从而大大减慢构建时间。有没有办法从标题中取出模板代码并返回到cpp文件中,这样小的实现更改不会导致重大的重建?

6 个答案:

答案 0 :(得分:21)

几种方法:

  • export keyword理论上可以提供帮助,但它支持得很差,并且在C ++ 11中被正式删除。
  • 显式模板实例化(请参阅herehere)是最直接的方法,如果您可以提前预测您需要哪些实例化(如果您不介意维护此列表) )。
  • Extern templates,已被多个编译器作为扩展支持。我的理解是,extern模板不一定允许您将模板定义移出头文件,但它们确实可以更快地进行编译和链接(通过减少模板代码必须实例化和链接的次数)。
  • 根据您的模板设计,您可以将其大部分复杂性转移到.cpp文件中。标准示例是一个类型安全的矢量模板类,它只包含void*的类型不安全的矢量;所有复杂性都存在于.cpp文件中的void*向量中。 Scott Meyers在 Effective C ++ (第42版“明智地使用私有继承”,第2版)中提供了更详细的示例。

答案 1 :(得分:17)

我认为一般规则适用。尝试减少代码部分之间的耦合。将太大的模板标题分解为一起使用的较小的函数组,因此整个事物不必包含在每个源文件中。

另外,尝试快速将标头置于稳定状态,也许可以针对较小的测试程序对其进行测试,因此当集成到更大的程序中时,它们不需要更改(太多)。

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

答案 2 :(得分:5)

  • 您可以获得支持export关键字的编译器,但这种情况不太可能持久。

  • 您可以使用explicit instantiation,但不幸的是,这需要您提前预测您将使用的模板类型。

  • 如果您可以从算法中分解模板化类型,可以将其放在自己的.cc文件中。

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

答案 3 :(得分:5)

首先,为了完整起见,我将介绍直截了当的解决方案:仅在必要时使用模板化代码,并将其基于非模板代码(在其自己的源文件中实现)。

然而,我怀疑真正的问题是你使用泛型编程,就像你使用典型的OO编程一样,最终得到一个膨胀的类。

我们举一个例子:

// "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
};

这让你感到震惊吗?可能不是。毕竟它看起来很简约。问题是,事实并非如此。可以在不失一般性的情况下考虑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];
}

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

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

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

但是,多亏了Koenig的查找,只要你把它们放在同一个命名空间中就可以把它们称为不合格,所以这只是习惯问题。

这个例子是人为的,但总的来说就是这样。请注意,由于它的通用性at.hpp从不必包含bigArray.hpp,并且仍然会产生紧密的代码,就好像它是一个成员方法一样,只是我们可以在其他容器上调用它。如果我们愿意的话。 / p>

现在,BigArray的用户如果不使用at.hpp则不需要包含std::out_of_range ...因此,如果您更改该文件中的代码,则会减少其依赖关系并且不会受到影响:例如,更改BigArray调用以显示文件名和行号,容器的地址,大小和我们尝试访问的索引。

另一个(不那么明显)的优点是,如果at的完整性约束被违反,那么{{1}}显然是不合理的,因为它不会弄乱类的内部,因此减少嫌犯人数。

这是许多作者推荐的,例如C++ Coding Standards中的Herb Sutters:

  

第44项:更喜欢写非会员非友好职能

已广泛用于 Boost ......但你必须改变你的编码习惯!

当然,您只需要包含您所依赖的内容,应该有静态C ++代码分析器报告包含但未使用的头文件,这有助于解决这个问题。

答案 4 :(得分:3)

您可以定义没有模板的基类,并在那里移动大部分实现。然后,模板化数组将仅定义代理方法,该方法使用基类来实现所有内容。

答案 5 :(得分:3)

将模板用作解决问题的技术会导致编译速度变慢。一个典型的例子是C中的std :: sort vs. qsort函数。该函数的C ++版本编译时间较长,因为它需要在每个翻译单元中进行解析,并且几乎每次使用此函数都会创建一个不同的实例。 (假设通常以闭合谓词的形式提供闭包类型)。

尽管可以预料到这些速度会下降,但是有些规则可以帮助您编写高效的模板。其中四个描述如下。

Chiel的规则

下面介绍的Chiel规则描述了对于编译器来说最困难的C ++结构。如果可能的话,最好避免使用这些结构以减少编译时间。

以下C ++功能/结构按编译时间降序排列:

  • SFINAE
  • 实例化功能模板
  • 实例化类型
  • 调用别名
  • 向类型添加参数
  • 为别名调用添加参数
  • 查找记忆类型

在设计和开发Boost.TMP时使用了基于上述规则的优化。尽可能避免使用顶级结构来进行快速模板编译。

下面是一些示例,这些示例说明了如何利用上面列出的规则。

减少模板实例化

让我们看一下std :: conditional。它的声明是:

template< bool B, typename T, typename F >
struct conditional;

每当我们更改为该模板提供的三个参数中的任何一个时,编译器都将不得不为其创建一个新实例。例如,想象以下类型:

struct first{};
struct second{};

现在,以下所有内容将以不同类型的实例化结束:

using type1 = conditional<true, first, second>;
using type2 = conditional<true, second, first>;
std::is_same_v<type1, type2>; // it’s false

using type3 = conditional<false, first, second>;
using type4 = conditional<false, second, first>;
std::is_same_v<type1, type2>; // it’s false

我们可以通过将条件实现更改为来减少实例化的数量:

template <bool>
struct conditional{
     template <typename T, typename F>
     using type = T;
};

template <>
struct conditional<false>{
     template <typename T, typename F>
     using type = F;
};

在这种情况下,编译器将为所有可能的参数创建两个“条件”类型的实例化。有关此示例的更多详细信息,请查看Odin Holmes' talk about the Kvasir library

创建显式模板实例

每当您怀疑模板实例将经常使用时,最好将其实例化。通常,std::stringstd::basic_string<char>的显式实例。

创建编译时算法的专业化知识

Kvasir-MPL专门针对较长类型的列表进行算法处理,以加快处理速度。您可以看到an example of this here。在此头文件中,排序算法专门用于255种类型的列表。手动专业化可加快长列表的编译速度。