为什么使用概念会使C++编译变慢?

12

它想要进行什么样的邪恶魔法!?!

我在听与Herb Sutter进行的问答环节时,有一个问题是关于概念的。Herb提到它会使编译器变慢(即使源代码没有改变),而且这个部分比模板的部分显着更大。

为什么会这样?我在哪里可以找到关于概念的文档?

3个回答

14

注意:以下答案(以及它回答的问题)涉及旧版C++0x的概念,与添加到C++20中的该功能版本没有多少关系。


首先,Herb并没有说概念本身会使编译变慢。他说将概念应用于C++标准库会使使用C++标准库的任何代码编译变慢。

这是由于以下几个原因:

1:限定模板需要编译时间。

当您像这样声明一个类:

template<typename T> class Foo {...};

编译器只是简单地解析Foo,做的事情非常少。即使进行两阶段查找,编译器在编译类Foo时也只是做了很少的工作。当然,它会将其存储以备后用,但初始遍历相对较快。

当您使用概念约束模板时:

template<ConceptName C> class Foo {...};
编译器必须执行某些操作。它必须事先检查类型C的每个使用是否符合概念ConceptName。这是编译器本可以推迟到实例化时处理的额外工作。 你拥有越多的概念检查,你就需要花费更多的编译时间来验证类型是否与概念匹配。 2:标准C++库使用了很多概念。 看一下迭代器概念的数量:输入、输出、前向、双向、顺序、连续。委员会正在考虑将它们分解成更多的部分。许多算法将具有多个版本,适用于不同的迭代器概念。 这还不包括范围概念(除输出迭代器概念外,每种迭代器概念都有一个),std::string的字符概念以及各种其他类型的概念。所有这些都必须被编译和检查。
使其快速的真正需要的概念是模块。编译器能够生成一个包含预先检查过的符号序列的模块文件,然后直接加载该文件,而无需经过标准编译过程。直接从解析到符号创建。 请记住:对于每个要#include的.cpp文件,编译器必须读取该文件并对其进行编译。即使文件每次这样做都是相同的东西,它仍必须认真读取文件并处理它。如果我们谈论概念化的std::vector,它必须执行模板的所有概念检查。它仍然必须执行您在编译时执行的所有标准符号查找等操作。 想象一下,如果编译器不必这样做。想象一下,它可以直接从磁盘加载一堆符号和定义。完全没有编译;只是将符号和定义带入供其他代码使用。 这就像预编译头文件,只不过更好。预编译头文件仅限于每个.cpp文件一个,而您可以使用任意数量的模块。 可悲的是,模块在C++0x的过程中很早就被取消了。没有模块,用概念约束标准库将始终比未约束版本编译得更慢。 请注意,Herb误解了模块的目的(这并不难理解,因为最初的特性概念正是他所谈论的事情:跨平台的DLL等)。它们的核心根本目的是帮助编译时间,而不是使跨平台DLL起作用。模块本身也不打算跨平台。

+!对于由概念引起的减速现象的解释很好,但是你是否有关于你所提出的概念视野的参考资料(与Herb Sutter提出的不同)? - Ben Voigt
@Ben Voigt:那是模块,而不是概念。这不是我的愿景,而是Daveed Vandevoorde在N2316中概述的最新模块论文的设计目标。它可以在此处作为PDF文件获得:http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2007/ - Nicol Bolas
是的,我指的是模块。工作组参考正是我所希望的。 - Ben Voigt
啊,我跳到了那一段,并听到了“conceptize” STL部分,这是我第一次听时错过的。不错。我认为这不会增加太多开销,因为代码将使用所述的方法/运算符,检查只会在启动阶段发生,而不是在函数中间给出一个奇怪的错误。好答案。 - user34537

9

由于这个问题相当陈旧(来自2011年),而且在撰写本文时(2020年)已经发布了概念,因此我想澄清一些事情,以免误导人们或阻止他们使用概念。

过去被认为的概念和现在发布的概念是完全不同的。C++20中发布的概念也被称为“概念lite”,因为它们包含比概念最初设计时减少的功能。那么一开始的概念失去了什么呢?

主要区别在于,概念的首要设计目的旨在检查模板的使用正确性和定义正确性。例如,假设您有一个具有类型Animal的模板,需要具有成员函数make_sound。您可以像下面这样想象一个受约束的函数模板:

template <typename Animal>
requires requires(Animal& x){
  x.make_sound();
}
int animal_tricks(Animal& x) {
  x.make_sound();
  x.do_trick();
}

现在,在最初的概念设计中,定义函数模板animal_tricks是不正确的,因为我们使用了一个do_trick成员函数,它不是所需表达式的一部分。然而,通过C++20概念lite,这种概念定义是合法的。在概念lite世界中,编译器不会检查animal_tricks函数模板的正确性,开发人员需要正确地指定类型的要求。
这种差异可能会对编译时间产生相当大的影响。2016年,有两篇论文考虑了概念进入C++17与否的原因:“为什么我想要概念,为什么我希望它们早日实现”“为什么我想要概念,但为什么它们应该比较晚实现”。它们都没有考虑性能问题,这说明当时性能并不是一个问题。
另外,当前的概念设计可能具有一些性能优势。根据Chiel的规则,编译中最慢的是SFINAE,因为它至少需要尝试实例化(通常)大量的类型,然后才能在稍后放弃它们。概念(取决于它们如何实现)可能不需要实例化任何模板,这实际上可能会成为性能优势。

0

你可能会在ConceptsGCC网站上找到有用的资源。这是他们正在构建的编译器(从GCC分支出来),以查看该概念是否可行。

我想开销来自于必须对各种语言结构执行彻底、普遍和递归的有效性检查,而且考虑到你可以指定相当丰富的约束条件,检查这些条件可能会变得非常昂贵。

有点像异常规范的噩梦版本!


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