【问题标题】:Constexpr vs Template Metaprogramming - performance differencesConsexpr 与模板元编程 - 性能差异
【发布时间】:2020-04-19 15:01:23
【问题描述】:

我可以看到 constexpr 正在赢得越来越多的领域,而使用它而不是模板元编程 (TMP) 的论据之一是性能。我的问题是:

  1. 有没有这方面的性能比较示例?

  2. 如果 TMP 更好,为什么不将 TMP 替换为 constexpr?

TMP 保证它在编译时运行,但也可以使用 constexpr 来完成。 constexpr 处理浮点数,而 TMP 不处理,对于我见过的所有示例,替换 TMP 的 constexpr 函数更容易阅读和使用更少的代码行。 constexpr 也允许条件和循环,而 TMP 只处理循环的递归“模拟”。

我之前在这里 (link) 询问过一个相关问题 - 我有 3 种不同的元编程方式 - 这里首选 constexpr 函数方式..

【问题讨论】:

  • 第三种选择的建议: 3. 为什么不根据情况混合使用 TMP 和 constexpr?
  • @max66 这不是我在我的一个示例中所做的吗?我仍然认为,与 TMP 或与 constexpr 混合的 TMP 相比,constexpr 函数仍然更可取。
  • @badaboomskey:“如果 TMP 更好,为什么不用 constexpr 替换它呢?” C++ 委员会正在努力做到这一点。但是constexpr 编码目前还没有 TMP 的能力。
  • @badaboomskey:像阶乘计算这样的例子只是玩具。 TMP 的存在主要是为了进行类型计算。 TMP 允许tuple_element_ttuple 类型中提取其中一种类型。纯constexpr 代码做不到;您可以构建一个constexpr 接口来键入计算,但工作的核心将被用于某种形式的 TMP。
  • constexpr 优势主要是代码可读性和在某些情况下减少编译器开销(例如,典型的编译时间阶乘可能比递归模板花费更少的时间来编译)。等效代码应该具有相似的性能,除非您遇到严重的性能问题,否则您应该使用使代码更易于阅读的任何内容。

标签: c++ metaprogramming template-meta-programming constexpr compile-time


【解决方案1】:

constexpr 函数通常更具可读性,因为它们是常规函数。

注意它可以在运行时使用,即使带有编译时间常量参数(在调用站点)。

constexpr int f(int n) { /*...*/ }

constexpr int f5 = f(5); // Compile time.
int f4 = f(4); // Runtime time (compiler can optimize as for non-constexpr functions though)

对于运行时性能,在常量表达式中使用时也有类似的情况,因为一切都是在编译时完成的。

对于编译时性能,TMP 的问题是实例化的数量,而constexpr 函数(带有常规参数)通常使用较少的实例化,因此 TMP 通常会使编译时间更长。

但是,如果需要记忆(如递归实现的斐波那契)

constexpr std::size_t fibonacci(std::size_t n)
{
    if (n < 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}
template <std::size_t N> struct Fibonacci
{
    constexpr std::size_t value = Fibonacci<N - 2>::value + Fibonacci<N - 1>::value;
};
template <> struct Fibonacci<0>
{
    constexpr std::size_t value = 1;
};
template <> struct Fibonacci<1>
{
    constexpr std::size_t value = 1;
};

TMP 应该重用实例化,而 constexpr 函数可能每次都重新计算它。在这种情况下,TMP 将编译得更快。

如果 TMP 更好,为什么不将 TMP 替换为 constexpr?

首先,我们不会放弃使用的 TMP,以实现复古兼容性。

那么,TMP 一般更适应返回类型或几种类型。

template <typename T> struct add_pointer
{
    using type = T*;
};

using int_ptr = add_pointer<int>::type;

而 constexpr 需要 decltype 和/或 std::declval

template <typename T> struct Tag { using type = T; };
constexpr Tag<T*> add_pointer(Tag<T>) {return {}; }

using int_ptr = decltype(add_pointer(Tag<int>{})::type;

【讨论】:

  • 那么constexpr if 呢?用于常规或 constexpr 函数中的类型以删除完整的专用模板函数... :-)
  • @Klaus:constexpr if 不适用于旧 TMP。所以似乎是OP问题的主题。它允许避免创建重载函数(可能具有复杂的 SFINAE)。不确定是否存在很大的编译时性能差异。
  • 我想说的是:目前有很多方法可以处理编译时代码(生成)。有 SFINAE 的纯模板,有 constexpr 函数,可能有重载,但也有 constexpr if。所有 3 个部分都可以使用和混合,很难说在产生的代码速度和质量以及编译时间和所有其他东西的情况下什么是最好的。与往常一样:如果您有疑问:测量:-) 我只是错过了 constexpr if,因为您可以替换例如任何递归模板特化也带有带有 constexpr 的模板(如果在里面)。就是这样。
  • @Klaus:如果你这样做,还有可变参数模板(适用于 TMP 和 constexpr 函数)和折叠它们的方式(递归方式和 C+ 之间存在差异+17 折叠方式)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-30
  • 2021-02-03
  • 2012-02-11
  • 2020-10-15
  • 2011-12-17
  • 1970-01-01
相关资源
最近更新 更多