【问题标题】:Optimizing compile-time performance by caching metafunctions通过缓存元函数优化编译时性能
【发布时间】:2013-06-16 17:09:14
【问题描述】:

假设我有以下元函数:

template <typename T>
struct make_pair {
    using type = std::pair<
        typename std::remove_reference<T>::type,
        typename std::remove_reference<T>::type
    >;
};

改为这样做(或其他)会提高编译速度吗?

template <typename T>
struct make_pair {
    using without_reference = typename std::remove_reference<T>::type;
    using type = std::pair<without_reference, without_reference>;
};

我看到了两种可能性:

  1. 编译器每次看到typename std::remove_reference&lt;T&gt;::type 时都必须做一些工作。使用中间别名具有某种“缓存”行为,它允许编译器只执行一次某些工作。

  2. 编译时性能是根据编译器必须执行的模板实例化数量来衡量的。因为std::remove_reference&lt;T&gt;::typestd::remove_reference&lt;T&gt;::type 引用相同的类型,所以在这两种情况下只需要一个模板实例化,因此两种实现都具有等效的WRT 编译时性能。

我认为 B 是对的,但我想确定一下。如果答案是编译器特定的,我最有兴趣知道 Clang 和 GCC 的答案。

编辑

我对测试程序的编译进行了基准测试,以获取一些可以使用的数据。测试程序做了这样的事情:

template <typename ...> struct result;    

template <typename T>
struct with_cache {
    using without_reference = typename std::remove_reference<T>::type;
    using type = result<without_reference, ..., without_reference>;
};

template <typename T>
struct without_cache {
    using type = result<
        typename std::remove_reference<T>::type,
        ...,
        typename std::remove_reference<T>::type
    >;
{ };

using Result = with[out]_cache<int>::type;

这些是程序 10 次编译的平均时间,result&lt;&gt; 中有 10 000 个模板参数。

                -------------------------
                | g++ 4.8 | clang++ 3.2 |
-----------------------------------------
| with cache    | 0.1628s | 0.3036s     |
-----------------------------------------
| without cache | 0.1573s | 0.3785s     |
-----------------------------------------

测试程序由here可用的脚本生成。

【问题讨论】:

  • 我认为任何猜测都无法取代实际测量。请发布一些时序图,然后我们可以创建一个很好的理论来解释它们。
  • 我看到一个关于 clang 的演讲,说他们为模板实例化而不是链接列表制作哈希表。不过我不知道他们是在和谁比较。
  • 一个不做记忆的template编译器将会非常慢。
  • @Yakk:另一方面,我看到 gcc 在某些输入上崩溃,因为它缓存了太多以至于超出了可用内存,而 clang 非常慢,但至少编译了野兽(当然,我说的是荒谬的输入:p)。

标签: c++ templates instantiation template-meta-programming boost-mpl


【解决方案1】:

我不能说所有编译器都是如此,但 GCC 以及很可能所有其他主要编译器都将使用 memoization。如果您考虑一下,它几乎必须这样做。

考虑下面的代码

&f<X, Y>::some_value == &f<X, Y>::some_value

这是必须的,因此编译器必须确保它不会重复定义方法和静态成员。现在可能有其他方法可以做到这一点,但这只是让我尖叫记忆;我什至看不到另一种实现方式(当然,我已经非常努力地考虑过)

当我使用 TMP 时,我希望会发生记忆。如果不这样做,那将是一个真正的痛苦,太慢了。我看到编译时性能主要差异的唯一方法是:a)使用更快的编译器,如 Clang(比 GCC 快 3 倍)并选择不同的算法。在我看来,小的常数因素在 TMP 中的影响甚至比在我的经验中对 C 或 C++ 的影响还要小。选择正确的算法,尽量不要做不必要的工作,尽量减少实例化的数量,并使用好的编译器(MSVC++ 真的很慢,而且远不符合 C++11,但 GCC 和 Clang 是相当好);这就是你真正能做的。

此外,您应该始终牺牲编译时间以获得更好的代码。过早的编译时优化比普通的过早优化更邪恶。如果由于某种原因性能变得严重阻碍开发,则可能会有例外;但是我从来没有听说过这样的案例。

【讨论】:

  • 这个答案还不错,但它并不是我真正想要的。我正在寻找更确定的东西,比如来自 GCC 或 Clang 开发人员的确认。另外,我问这个问题的原因是因为我一直在研究元编程库并优化那里的编译时间是至关重要的。
  • @LouisDionne 在这里聚会有点晚了,但 GCC 确实缓存了模板实例化。它们存储在与显式特化相同的数据结构中。参考pt.c中find_with_hash的用法(github.com/gcc-mirror/gcc/blob/master/gcc/cp/pt.c
猜你喜欢
  • 2017-05-27
  • 1970-01-01
  • 1970-01-01
  • 2019-06-19
  • 1970-01-01
  • 2017-10-03
  • 2023-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多