为什么概念使 C++编译速度变慢?
它到底想施展什么邪恶的魔法啊!!
我正在听赫伯·萨特的问答环节,其中一个问题是关于概念。 Herb 提到它使编译器变慢(而源代码保持不变),并且该部分比模板部分大得多。
为什么要这样做?我在哪里可以找到有关概念的文档?
What kind of evil magic is it trying to do!?!
I was listening to a Q&A session with herb sutter and one question was about concepts. Herb mention it made compilers slower (while the source remains unchanged) and the section was significantly larger then the section on templates.
Why does it do this? where can i find documentation on concepts?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
注意:以下答案(及其回答的问题)涉及旧的 C++0x 版本概念,与 C++20 中添加的功能版本关系不大。
首先,Herb 并没有说概念本身会使编译速度变慢。他说,概念化 C++ 标准库会使任何使用 C++ 标准库的代码编译速度变慢。
其原因可以归结为几个方面。
1:约束模板需要编译时间。
当您声明这样的类时:
编译器只是解析 Foo 并执行很少的操作。即使使用两阶段查找,编译器在类 Foo 的编译中也不会执行大量操作。当然,它会存储它供以后使用,但初始传递相对较快。
当您确实用一个概念约束模板时:
编译器必须做一些事情。它必须预先检查类型
C
的每次使用是否符合概念ConceptName
。这是编译器将推迟到实例化时间的额外工作。您进行的概念检查越多,用于验证类型是否与概念匹配的编译时间就越多。
2:标准C++库使用了很多概念。
看看迭代器概念的数量:输入、输出、正向、双向、顺序、连续。委员会正在考虑将它们分解为更多的部分。许多算法对于不同的迭代器概念会有多个版本。
这不包括范围概念(除了输出之外,每种迭代器概念都有一个范围概念)、std::string 的字符概念以及各种其他类型的事物。所有这些都必须进行编译和检查。
使其快速运行真正需要的概念是模块。编译器能够生成包含一系列预先检查的符号的模块文件,然后直接加载该文件,而无需经过标准编译过程。直接从解析到符号创建。
请记住:对于您 #include 的每个 .cpp 文件,编译器必须读取该文件并编译它。尽管每次执行此操作时文件都是相同的内容,但它仍然必须尽职尽责地读取文件并处理它。如果我们谈论的是概念化的
std::vector
,它必须对模板进行所有概念检查。它仍然需要执行编译时执行的所有标准符号查找。等等。想象一下,如果编译器不必这样做会怎样。想象一下,如果它可以直接从磁盘加载一堆符号和定义。根本不需要编译;只是引入符号和定义供其他代码使用。
它就像预编译头一样,只是更好。预编译头被限制为每个 .cpp 文件只能有一个,而您可以根据需要使用任意多个模块。
遗憾的是,模块在这个过程的早期就被从 C++0x 中删除了。如果没有模块,用概念约束标准库的编译速度总是比不受约束的版本慢。
请注意,Herb 误解了模块的用途(这并不难,因为该功能的大多数最初概念都是他谈到的内容:跨平台 DLL 等)。它们的核心根本目的是帮助编译时间,而不是使跨平台 DLL 工作。模块本身也不是跨平台的。
Note: the following answer (and the question it answers) pertains to the old C++0x version of concepts and has little relation to the version of the feature added to C++20.
First of all, Herb didn't say that concepts themselves made compiling slower. He said that conceptizing the C++ standard library made any code using the C++ standard library compile slower.
The reason for that comes down to several things.
1: Constraining templates takes compile time.
When you declare a class like this:
The compiler simply parses Foo and does very little. Even with two-phase lookup, the compiler simply doesn't do a whole lot in the compilation of class Foo. It stores it for later, of course, but the initial pass is relatively fast.
When you do constrain the template with a concept:
The compiler must do some things. It must check up front that every use of the type
C
conforms to the conceptConceptName
. That's extra work that the compiler would have deferred until instantiation time.The more concept checking you have, the more compile time you spend to verify that the types match the concepts.
2: The standard C++ library uses a lot of concepts.
Look at the number of iterator concepts: input, output, forward, bidirectional, sequential, contiguous. And the committee was considering breaking them down into many more than that. Many algorithms would have multiple versions for different iterator concepts.
And this doesn't include range concepts (of which there is one for every kind of iterator concept except output), character concepts for std::string, and various other kinds of things. All of these have to be compiled and checked.
What concepts really needed to make it fast is modules. The ability for the compiler to generate a module file that contains a sequence of pre-checked symbols, and then load that file directly without having to go through the standard compilation process. Straight from parsing to symbol creation.
Remember: for each .cpp file you #include , the compiler must read that file and compile it. Even though the file is the same thing every time it does this, it still must dutifully read the file and process it. If we're talking about a concept-ized
std::vector
, it has to do all of the concept checking of the template. It still has to do all of the standard symbol lookup you do when compiling. And so forth.Imagine if the compiler didn't have to do this. Imagine if it could just load a bunch of symbols and definitions directly from the disk. No compiling at all; just bringing in symbols and definitions for other code to use.
It would be like precompiled headers only better. Precompiled headers are restricted to only have one per .cpp file, whereas you can use as many modules as you like.
Sadly, modules was yanked pretty early in the process from C++0x. And without modules, constraining the standard library with concepts will always compile more slowly than the unconstrained version.
Note that Herb misunderstands the purpose of modules (not hard, since most of the initial concepts of the feature were the things he talked about: cross-platform DLLs and such). Their core fundamental purpose is to help compile times, not to make cross-platform DLLs work. Nor is it intended that modules themselves be cross-platform.
由于这个问题已经很老了(从 2011 年开始),并且概念是在撰写本文时最近发布的(2020 年),因此我想澄清一些事情,只是为了不误导人们或阻止他们使用概念。
以前考虑的概念和现在发布的概念是完全不同的存在。 C++20 中发布的概念也称为“concepts lite”,因为与概念的初始设计相比,它们包含减少的功能。那么,概念中被剥夺了什么?
主要区别在于,概念的初步设计不仅旨在检查模板使用的正确性,而且还旨在检查该模板定义的正确性。例如,假设您有一个
Animal
类型的模板,它需要具有成员函数make_sound
。您可以像这样想象一个受约束的函数模板:现在,通过最初的概念设计,函数模板
animal_tricks
的定义将是不正确的,因为我们使用的是do_trick
成员函数,它不是所需表达式的一部分。对于 C++20 Concepts Lite,这个概念的定义很好。编译器不会检查animal_tricks
函数模板的正确性,因为在概念精简的世界中,由开发人员正确指定类型的要求。这种差异可能会导致编译时间产生很大的差异。 2016年,有两篇论文考虑了概念进入C++17与否的原因:
“为什么我想要 Concepts,以及为什么我希望他们早点而不是晚点” 并且
“为什么我想要概念,但为什么他们应该来晚一点而不是早一点。”
两者都没有考虑性能,所以这是一个很好的指标,表明这在当时不是一个问题。
此外,当前的概念设计可能会带来一些性能优势。根据Chiel规则,编译中最慢的是SFINAE,因为它至少需要尝试(通常)实例化大量类型,但后来又放弃它们。概念(取决于它们的实现方式)可能不需要实例化任何模板,这实际上可能最终会带来性能优势。
Since this question is pretty old (from 2011) and concepts were recently released as of this writing (2020), I would like to clarify a couple of things, just to not mislead people or discourage them from using concepts.
Concepts that used to be considered and concepts being released now are quite different beings. Concepts released in C++20 are also known as “concepts lite” as they include reduced features compared to the initial design of concepts. So, what was taken away from concepts?
The main difference is that the primary design of concepts was intended for checking not only the correctness of the usage of a template but also the correctness of the definition of this template. For example, suppose you have a template with type
Animal
, that needs to have the member functionmake_sound
. You can imagine a constrained function template like so:Now, with the initial design of concepts, the definition of the function template
animal_tricks
would be incorrect because we are using ado_trick
member function, which was not part of the required expression. With C++20 concepts lite, this definition of concept is fine. The compiler will not check the correctness of theanimal_tricks
function template because, in a concepts-lite world, it’s up to the developer to correctly specify the requirements on the type.That difference can make quite a big difference in the compilation time. In 2016, there were two papers that considered the reasons for concepts to enter C++17 or not:
“Why I want Concepts, and why I want them sooner rather than later” and
“Why I want Concepts, but why they should come later rather than sooner.”
Neither even considered performance, so it’s a good indicator that it was not an issue back then.
Also, the current concepts design might come with some performance advantages. According to the rule of Chiel, the slowest thing in compilation is SFINAE because it needs to at least try to instantiate (usually) a significant amount of types, only to abandon them later on. Concepts (depending on how they are implemented) might not need to instantiate any templates, which in fact might end up being a performance advantage.
您可能会在 ConceptsGCC 网站 上找到有用的资源。这就是他们构建的编译器(从 GCC 分叉出来),以查看这个概念(请原谅双关语)是否可行。
我想费用来自于必须对各种语言结构执行彻底、普遍和递归的有效性检查,并且考虑到您可以指定一组相当丰富的约束,检查这些约束可能会变得非常昂贵。
有点像异常规范的噩梦版本!
You might find useful resources on the ConceptsGCC website. That's the compiler (forked off GCC) which they were building to see if the concept (pardon the pun) was feasible.
I imagine the expense comes from having to perform thorough and ubiquitous and recursive validity checks on all sorts of language constructs, and given that you could specify a pretty rich set of constraints, checking for those can get very expensive.
A bit like a nightmare version of exception specifications!