【问题标题】:Are there optimized c++ compilers for template use?是否有用于模板使用的优化 C++ 编译器?
【发布时间】:2010-10-09 14:22:28
【问题描述】:

C++ 模板因其强大的功能而成为我日常工作中的福音。但是不能忽略大量使用模板(hello 元编程和 Boost 库)导致的(非常非常非常长的)编译时间。我已经阅读并尝试了很多手动重组和修改模板代码以使其尽快编译的可能性。

现在我想知道是否有任何 c++ 编译器尝试最小化解释模板类所需的时间。我可能错了,但我觉得我所知道的编译器只是在他们以前的版本中添加了模板解释。

我的问题是:

  • c++ 模板代码是不是很难解释,没有太多需要优化的地方? (我非常怀疑)
  • 是否有真正优化“c++ 模板”解释的 c++ 编译器?
  • 是否有开发新一代 c++ 编译器的项目可以对此进行优化?
  • 如果您要参与这样的项目,您的指导方针是什么?

【问题讨论】:

  • 我宁愿让编译器开发人员专注于优化生成的代码,并利用并行性(SMP、distcc、Xoreax IncrediBuild 等)来加快构建速度。
  • 为什么不让他们解决这两个问题?我期望从针对模板优化的 c++ 编译器获得的收益远高于我从使用并行性中获得的收益(我已经这样做了),除非你被无数台计算机包围着等待你编译代码(我没有' t) !

标签: c++ templates compiler-construction


【解决方案1】:

这不会是您想要的答案,但 Walter Bright 是第一个原生 C++ 编译器的主要开发人员,也是一个优化的 c++ 编译器。毕竟他编写了自己的编程语言 D。它基本上是对 C++ 的改进,并且可以更好地处理模板。

我不知道有哪个 c++ 编译器针对模板使用进行了优化。

【讨论】:

  • IIRC 有人用 Boost::Spirit(元编程解析器生成器)做了 50 个生产语法,编译需要 17 个小时。我有 250 个生产语法,可以在 7 分钟 内使用 D 等价物进行编译
  • 现在这就是我所期待的收获!但我无法将所有现有代码都更改为 D 语言!
【解决方案2】:

我认为模板本身并没有那么复杂。我们将看到何时在 c++0x 等中引入概念,但目前,模板只是(几乎)作为宏,所以真正的问题不在于您是否为模板优化了 c++ 编译器。问题是模板在编译时确实会生成如此大量的代码,导致编译速度变慢。

【讨论】:

  • 你能详细说明你认为是什么让编译变慢了吗?我个人使用模板作为隔离项目中类开发的一种方式。如果只有一个项目,我可以在没有任何模板的情况下做同样的事情。但是有很多项目,因此有很多模板!
  • Benoît,使用模板编译 速度较慢。请注意,当您使用 vector 并在其上调用一些函数时,编译器会采用模板化代码并将其编译为 int 类型。如果你使用,比如说,vector,也是一样的。但是通常编译速度通常应该是...
  • ... 第二个问题,很多时候,使用模板您可以开发更简洁的解决方案来解决问题,甚至可以解决没有模板无法解决的问题。所以总结一下:模板使编译速度变慢,但它们是 C++ 中的一个关键抽象。
  • 相信我,我知道使用模板时编译速度会慢多少。在我的第一条评论中,我想说的是我没有使用每个模板类的多个实例。恰恰相反!我基本上使用策略类作为模板参数。我正在寻找原因......
  • ...为什么“模板 c++”编译需要更长的时间,即使我 不是 在你描述的情况下。顺便说一句,我知道模板带来了什么。我只想要这一切,编译速度和模板功能。我强烈地觉得这是可以实现的。因此我的问题。
【解决方案3】:

试试Incredibuild。它极大地减少了编译/构建时间。

该产品基本上使 Visual C++ 能够利用空闲周期跨组织中的多台计算机进行构建。我在包含大量模板代码的大型项目 (500 kloc) 上使用了 Incredibuild,并且在构建时间上得到了很好的加速。

【讨论】:

    【解决方案4】:

    这确实不是您问题的答案。这更像是一个侧面观察。

    我也不是 C++ 语言律师,所以我可能无法了解一些细节。

    但是,粗略的想法应该是正确的。

    C++ 编译器需要这么长时间来编译模板元程序的主要原因是模板元程序的指定方式。

    它们没有直接指定为您希望编译器在编译时运行的代码。以计算类型列表长度为例。

    如果你能写出这样的代码:

    compile_time size_t GetLength(TypeList * pTypeList)
    {
        return DoGetLength(pTypeList, 0);
    }
    
    compile_time size_t DoGetLength(TypeList * pTypeList, size_t currentLength)
    {
        if (pTypeList)
        {
            return DoGetLength(pTypeList->Next, ++currentLength);
        }
        else
        {
             return currentLength;
        }
    }
    

    这是与使用它的代码分开编译的方式,并通过某种语法暴露给语言,然后编译器将能够非常快速地执行它。

    这只是一个简单的递归函数调用。

    有可能设计出允许这些事情发生的语言。大多数这样做的(如 lisp)都是动态类型的,但也可以使用静态类型。但是,它不太可能是您在 C++ 中看到的实现方式。

    然而,C++ 的问题是代码写成这样:

    template <typename First,  typename Second>
    struct TypeList
    {
        typedef First Head;
        typedef Second Tail;
    };
    
    template <>
    struct ListSize<NullType>
    {
        enum {  size = 0  };
    };
    
    template <typename Head, typename Tail>
    struct ListSize<TypeList<Head, Tail> >
    {
        enum {  size = 1 + ListSize<Tail>::size  };
    };
    

    为了让编译器“执行”元程序,它必须:

    1. 为“size”枚举值的初始值构造依赖图
    2. 为图中的每条边构造一个模板类型
    3. 绑定每个构造模板类型引用的所有符号
    4. 对依赖图进行拓扑排序
    5. 遍历图形并计算常数

    这比仅仅运行 O(N) 递归算法要昂贵得多。

    最坏的情况类似于 O(N * M * L),其中 N 等于列表的长度,M 是范围嵌套的级别,L 是每个范围内的符号数。

    我的建议是尽量减少您使用的 C++ 模板元编程的数量。

    【讨论】:

    • +1。我喜欢你的例子。这正是我的想法。模板元编程是用编译器作为解释器的“只是”脚本。虽然它不能在 O(N) 中处理,但我不相信我们会接近你最坏的情况......
    【解决方案5】:

    我希望通过可变参数模板/右值引用来加快编译模板化代码的速度。今天,如果我们想编写在编译时做某事的模板代码,我们就会滥用语言的规则。我们创建了几十个重载和模板特化来产生我们想要的结果,但不是以一种告诉编译器我们的意图的方式。因此,编译器在构建时几乎没有捷径可走。见Motivation for variadic templates

    是否有开发新一代 c++ 编译器的项目可以对此进行优化?

    是的,CLang 是 LLVM 编译器基础架构的 C 语言前端。 CLang 和 LLVM 都是使用 C++ 编码的。 CLang 的开发者中有 Douglas Gregor,他是几个 C++1x 语言提案(如可变参数模板和概念)的作者。作为参考,请参阅 Douglas Gregor 的 clang 针对 GCC 的测试

    以下是在 Clang 和 GCC 4.2 中模板实例化的一些快速 n-dirty 性能结果。测试非常简单:测量通过模板元程序计算第 N 个斐波那契数的翻译单元的编译时间(-fsyntax-only)。 Clang 似乎随着实例化的数量线性(或接近)缩放。而且,虽然您在图表中看不到它,但 Clang 一开始比 GCC 快 2 倍多一点 (Fibonacci&lt;100&gt;)。

    CLang 仍在其early days 中,但我认为它有很好的机会成为出色的 C++ 编译器。

    【讨论】:

      【解决方案6】:

      模板的主要问题如下:

      您不能(通常)将模板类的定义与其声明分开并将其放在 .cpp 文件中。

      结果:一切都在头文件中。 每当您包含头文件时,您都会包含大量代码,这些代码在正常情况下会很好地分离到 .cpp 文件中并单独编译。每个编译单元都包含一些标头,因此,对于模板,每个编译单元都包含很多代码,或者通过包含的标头包含几乎所有项目。

      如果这是您的问题,那么请看这里的相关问题:

      它有一个very good answer,它确实解决了这个问题

      基本上,它涉及将您需要的模板实例化一次,然后将它们编译成一个目标文件。稍后您可以链接它,并且您不必在任何地方都包含该代码。它被分成一个编译的目标文件。注意:仅当您仅使用少数实例化类型的模板时才有意义(例如,您的程序中只需要 MyType&lt;int&gt;MyType&lt;double&gt;)。

      它使用g++ 标志-fno-implicit-templates

      该技术非常有用,我认为应该将其合并到 C++ 常见问题解答中:[35.12] Why can't I separate the definition of my templates class from it's declaration and put it inside a .cpp file?

      【讨论】:

      • 你当然是对的。一切都在头文件中。但这并不意味着它不能在单独的头文件中。我每天都在使用分隔在两组头文件中的模板类:声明和实现...
      • ...为什么不添加另一个前向声明?无论如何,实际的 cpp 文件包含实现和实例化的模板参数。它确实工作得很好,但不幸的是,就编译速度而言,它并没有太大帮助。
      • 重新评论您的第一条评论:仅将模板声明和实现放在单独的头文件中不会提高编译速度。如果您不使用我的回答中描述的技巧,您仍然必须为每个编译单元都包含两者。
      【解决方案7】:

      gold linker 可以帮助将链接时间减少大约 5 倍,从而可以减少整体编译时间。它特别有用,因为链接不能像编译那样并行化。

      (不是直接的答案,但希望这会有所帮助)。

      【讨论】:

        【解决方案8】:

        g++ 4.5 似乎在处理模板方面取得了巨大的进步。这是两个不可避免的变化。

        • “在打印类模板特化的名称时,G++ 现在将省略任何来自默认模板参数的模板参数。”这可以被认为是一个微妙的修改,但它会对使用 c++ 模板的开发产生巨大的影响(听说过不可读的错误消息......?不再有!)

        • “使用模板的代码的编译时间现在应该与实例化的数量成线性比例,而不是二次方。”这将严重破坏反对使用 C++ 模板的编译时参数。

        查看gnu site了解完整信息

        实际上,我已经想知道 C++ 模板是否还有问题!嗯,是的,有,但现在让我们关注光明的一面!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-04-29
          • 2017-06-26
          相关资源
          最近更新 更多