【问题标题】:Why does concepts make C++ compile slower?为什么概念会使 C++ 编译速度变慢?
【发布时间】:2011-06-22 15:46:04
【问题描述】:

它到底想做什么邪恶的魔法!?!

我在听Q&A session with herb sutter,其中一个问题是关于概念的。 Herb 提到它使编译器变慢(而源代码保持不变)并且该部分明显大于模板部分。

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

【问题讨论】:

    标签: c++ c++11 c++-concepts


    【解决方案1】:

    注意:以下答案(及其回答的问题)与旧 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:那是模块,而不是概念。这不是我的愿景;这是 Daveed Vandevoorde 在 N2316 中概述的最新模块论文的设计目标。在此处以 PDF 格式提供:open-std.org/JTC1/SC22/WG21/docs/papers/2007
    • 是的,我的意思是模块。工作组参考正是我所希望的。
    • 啊。我跳到那个部分,听到了当我第一次听时滑过的“概念化”stl部分。好的。我认为这不会增加太多开销,因为代码将使用上述方法/运算符,并且检查只会发生在开始阶段,而不是在函数中间发生奇怪错误。很好的答案。
    【解决方案2】:

    由于这个问题已经很老了(从 2011 年开始),并且在撰写本文时(2020 年)最近才发布了一些概念,我想澄清几件事,以免误导人们或阻止他们使用概念。

    过去考虑的概念和现在发布的概念是完全不同的存在。 C++20 中发布的概念也称为“概念精简版”,因为与概念的初始设计相比,它们包含的功能有所减少。那么,从概念中拿走了什么?

    主要区别在于概念的主要设计不仅用于检查模板使用的正确性,还用于检查该模板定义的正确性。例如,假设您有一个类型为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 精简版概念,这个概念的定义很好。编译器不会检查animal_tricks 函数模板的正确性,因为在concepts-lite 世界中,由开发人员正确指定类型的要求。 这种差异会在编译时间上产生相当大的巨大差异。 2016 年有两篇论文考虑了概念进入 C++17 与否的原因: “Why I want Concepts, and why I want them sooner rather than later”“Why I want Concepts, but why they should come later rather than sooner.” 甚至都没有考虑性能,因此这是一个很好的指标,表明当时它不是问题。

    此外,当前的概念设计可能会带来一些性能优势。根据the rule of Chiel 的说法,编译中最慢的事情是 SFINAE,因为它至少需要尝试(通常)实例化大量类型,然后才放弃它们。概念(取决于它们的实现方式)可能不需要实例化任何模板,这实际上可能最终成为性能优势。

    【讨论】:

      【解决方案3】:

      您可能会在ConceptsGCC website 上找到有用的资源。那是他们正在构建的编译器(从 GCC 分叉),以查看这个概念(请原谅双关语)是否可行。

      我认为开销来自于必须对各种语言结构执行彻底、普遍和递归的有效性检查,并且鉴于您可以指定一组非常丰富的约束,检查这些约束可能会变得非常昂贵。

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

      【讨论】:

        猜你喜欢
        • 2020-02-11
        • 1970-01-01
        • 1970-01-01
        • 2018-05-22
        • 1970-01-01
        • 1970-01-01
        • 2021-10-06
        • 2021-06-19
        • 1970-01-01
        相关资源
        最近更新 更多