通过缓存元函数来优化编译时性能

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<T>::type 时都需要做一些工作。使用中间别名具有某种“缓存”行为,使得编译器只需执行一次某些工作。

  2. 编译时性能是以编译器必须执行的模板实例化数量为单位衡量的。因为 std::remove_reference<T>::type 引用与 std::remove_reference<T>::type 相同的类型,所以在两种情况下都只需要一个模板实例化,因此这两种实现在编译时性能方面是等效的。

我认为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;

这些是在 result<> 中使用10,000个模板参数进行10次编译的平均时间。

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

测试程序是由一个可在这里获取的脚本生成的。


4
我认为,任何推测都无法取代实际的测量。请提供一些时间数据,然后我们可以创造出一个好的理论来解释它们。 - Balog Pal
我看了一个关于clang的演讲,说他们为模板实例化制作了哈希表,而不是链表。但我不知道他们在与谁进行比较。 - Mooing Duck
一个不进行记忆化的模板编译器将会非常慢。 - Yakk - Adam Nevraumont
@Yakk:另一方面,我曾见过因为缓存了太多内容以至于超出了可用内存而导致崩溃的gcc,而clang则非常缓慢,但至少可以编译这个“野兽”(当然,我说的是荒谬的输入:p)。 - Matthieu M.
1个回答

2

我不能说所有编译器都是这样的,但GCC以及其他大型编译器很可能使用记忆化技术。如果您认真思考一下,它几乎必须这样做。

请考虑以下代码:

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

这是必须为真的,因此编译器必须确保它不会重复定义方法和静态成员。现在可能有其他方法来实现这一点,但对我来说,这只是一个明显的记忆化问题;我没有看到另一种实现方法(尽管我已经非常努力地思考过)。

当我使用TMP时,我期望进行记忆化。如果没有记忆化,那将是一个真正的痛苦,速度太慢了。我看到编译时间性能有很大差异的唯一方法是:要么使用更快的编译器,例如Clang(比GCC快3倍),要么选择不同的算法。在我的经验中,小的常数因素似乎比C或C++还要不重要。选择正确的算法,避免不必要的工作,尽量减少实例化数量,使用好的编译器(MSVC++非常慢,远离C++11兼容,但GCC和Clang非常好);这就是你真正可以做的事情。

此外,你应该总是牺牲编译时间以换取更好的代码。过早地进行编译时间优化比纯粹的过早优化更加恶劣。如果由于某些原因性能对开发变得非常严重,那可能有例外;但我从未听说过这种情况。


1
这个答案还不错,但并不是我真正想要的。我正在寻找更确切的东西,比如来自GCC或Clang开发者的确认。另外,我之所以问这个问题,是因为我一直在研究元编程库,并且优化编译时间非常关键。 - Louis Dionne
@LouisDionne 迟来的回复,但 GCC 确实会缓存模板实例化。它们存储在与显式特化相同的数据结构中。请参考 pt.c 中 find_with_hash 的用法(https://github.com/gcc-mirror/gcc/blob/master/gcc/cp/pt.c)。 - Barrett Adair

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