【问题标题】:C++11 Performance: Lambda inlining vs Function template specializationC++11 性能:Lambda 内联与函数模板特化
【发布时间】:2019-02-08 18:10:47
【问题描述】:

我的问题是对此进行扩展:Why can lambdas be better optimized by the compiler than plain functions?

重申一下,结论是 lambda 创建了编译器可以轻松内联的不同特化,而函数指针并不容易内联,因为一组函数原型只有一个特化。考虑到这一点,函数指针模板是否会像/更快的 lambdas 一样快?

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

template <class F>
int operate(int a, int b, F func)
{
    return func(a, b);
}

template <int func(int, int)>
int operateFuncTemplate(int a, int b)
{
    return func(a, b);
}

int main()
{
    // hard to inline (can't determine statically if operate's f is add or sub since its just a function pointer)
    auto addWithFuncP = operate(1, 2, add);
    auto subWithFuncP = operate(1, 2, sub);

    // easy to inline (lambdas are unique so 2 specializations made, each easy to inline)
    auto addWithLamda = operate(1, 2, [](int a, int b) { return a + b; });
    auto subWithLamda = operate(1, 2, [](int a, int b) { return a - b; });

    // also easy to inline? specialization means there are 2 made, instead of just 1 function definition with indirection?
    auto addWithFuncT = operateFuncTemplate<add>(1, 2);
    auto subWithFuncT = operateFuncTemplate<sub>(1, 2);
}

因此,如果我可以按性能等级对这些进行排名,那么:

operatorFuncTemplate >= operate&lt;LAMBDA&gt; >= operate&lt;FUNCTIONPTR&gt;

是否存在这种关系在非平凡示例中可能失败的情况?

【问题讨论】:

  • 可以使用godbolt分析生成的代码
  • @OneManMonkeySquad 我查看了 MSVC++ 中的反汇编,但所有内容都在发布时预编译,而且我还不够熟练,无法准确理解在调试模式下内联发生的位置。此外,我不知道这个问题如何随着实际函数和 lambdas 扩大。
  • 你当然是对的 :)

标签: c++ performance templates lambda inline


【解决方案1】:

如果编译器可以跟踪“这个函数指针指向这个函数”,编译器可以通过函数指针内联调用。

有时编译器可以做到这一点。有时他们不能。

除非您将 lambda 存储在函数指针 std::function 或类似的类型擦除包装器中,否则调用 lambda 的编译器知道 lambda 的类型,因此知道 lambda 的主体。编译器可以简单地内联函数调用。

使用函数模板不会改变这一点,除非参数是constexpr,就像函数非类型模板参数一样:

template <int func(int, int)>

这是一个例子。这里保证函数体中的函数模板在编译时是已知的。

但是,将 func 传递到其他任何地方,编译器可能会忘记它。

在任何情况下,任何速度差异都将高度依赖于上下文。有时,内联 lambda 导致的较大二进制大小会导致比无法内联函数指针更慢,因此性能可能会相反。

任何像你试图提出的普遍主张有时都会出错。

【讨论】:

  • 好的,我很欣赏关于膨胀二进制大小的见解。我对函数模板的思考过程是不涉及间接。当我使用operateFuncTemplate 时,add 与之前的operate(1,2,add) 调用不同,不是间接函数调用。
  • 另外,如果我做了一个概括,你怎么看?例如,在大多数情况下,就速度而言,operatorFuncTemplate >= operate&lt;LAMBDA&gt; >= operate&lt;FUNCTIONPTR&gt;(但当然并非总是如此,并且需要实际的性能测试)。我想听听你的意见。
  • addWithFuncP 和 addWithLamdaP 肯定是一样的。
  • 嗯,实际上是在重读答案之后。我想我会完全修改我的概括。我决定这样做是因为对于简单的示例,(我认为)编译器可能会内联所有情况,并且对于具有多个函数专业化的较大示例,可能会出现不同的问题,例如二进制大小。
  • @MichaelChoi 我在中间添加了一些段落——非类型模板参数确实像 lambda 一样容易内联。我误读了部分问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-18
  • 2010-09-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多