【问题标题】:Understanding the overhead of lambda functions in C++11了解 C++11 中 lambda 函数的开销
【发布时间】:2013-09-08 06:24:46
【问题描述】:

Why C++ lambda is slower than ordinary function when called multiple times?C++0x Lambda overhead 中已经提到了这一点 但我认为我的例子与前者的讨论有些不同,与后者的结果相矛盾。

在我的代码中搜索瓶颈时,我发现了一个递归模板函数,它使用给定的处理器函数处理可变参数列表,例如将值复制到缓冲区中。

template <typename T>
void ProcessArguments(std::function<void(const T &)> process)
{}

template <typename T, typename HEAD, typename ... TAIL>
void ProcessArguments(std::function<void(const T &)> process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

我将使用此代码的程序的运行时间与 lambda 函数以及使用移动指针将参数复制到全局缓冲区的全局函数进行了比较:

int buffer[10];
int main(int argc, char **argv)
{
  int *p = buffer;

  for (unsigned long int i = 0; i < 10E6; ++i)
  {
    p = buffer;
    ProcessArguments<int>([&p](const int &v) { *p++ = v; }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  }
}

用 g++ 4.6 和 -O3 编译,用工具测量时间在我的机器上需要超过 6 秒,而

int buffer[10];
int *p = buffer;
void CopyIntoBuffer(const int &value)
{
  *p++ = value;
}

int main(int argc, char **argv)
{
  int *p = buffer;

  for (unsigned long int i = 0; i < 10E6; ++i)
  {
    p = buffer;
    ProcessArguments<int>(CopyIntoBuffer, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  }

  return 0;
}

大约需要 1.4 秒。

我不明白幕后发生的事情解释了时间开销,我想知道是否可以更改某些内容以使用 lambda 函数而无需支付运行时费用。

【问题讨论】:

  • 那么全球的真的慢吗?你说它是 6s vs. 1.4 的基于 lambda 的,但是最后一句话没有意义。
  • 在进行分析时,您是否查看了生成的程序集列表?
  • process 传递给const 引用,像这样void ProcessArguments(const std::function&lt;void(const T &amp;)&gt; &amp;process),有什么不同吗?
  • @cmaster 好吧,把你的马留在这里。除了这个基准有缺陷的事实之外——即使这是真的,它也只对闭包起作用。非闭包 lambda 有 no 开销。这是事实。并且闭包也不会,在通常的代码中 - OP 是作弊,非 lambda 代码使用全局对象,这通常不是一个选项:通常你必须手动实现闭包,然后你再一次具有与 lambda 完全相同的开销。如果有的话,这是反对使用std::function(在特定情况下)的论据,而不是反对 lambdas。

标签: c++ performance lambda variadic-templates


【解决方案1】:

这里的问题是你对 std::function 的使用。 您通过复制发送它,因此复制其内容(并在展开参数时递归地执行此操作)。

现在,对于指向函数的指针,内容就是指向函数的指针。 对于 lambda,内容至少是指向您捕获的函数 + 引用的指针。这是复制的两倍。另外,由于 std::function 的类型擦除,复制任何数据很可能会更慢(不是内联)。

这里有几个选项,最好的可能不是传递 std::function,而是传递模板。好处是您的方法调用更有可能被内联,std::function 不会发生类型擦除,不会发生复制,一切都非常好。像这样:

template <typename TFunc>
void ProcessArguments(const TFunc& process)
{}

template <typename TFunc, typename HEAD, typename ... TAIL>
void ProcessArguments(const TFunc& process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

第二个选项做同样的事情,但通过复制发送process。现在,复制确实发生了,但仍然整齐地内联。

同样重要的是process' body 也可以内联,特别是对于 lamda。根据复制 lambda 对象的复杂性及其大小,通过副本传递可能会也可能不会比通过引用传递更快。它可能会更快,因为编译器可能比本地副本更难推理引用。

template <typename TFunc>
void ProcessArguments(TFunc process)
{}

template <typename TFunc, typename HEAD, typename ... TAIL>
void ProcessArguments(TFunc process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

第三个选项是,好吧,尝试通过引用传递 std::function 。这样您至少可以避免复制,但不会内联调用。

以下是一些性能结果(使用 ideones 的 C++11 编译器)。 请注意,正如预期的那样,内联 lambda 主体为您提供了最佳性能:

Original function:
0.483035s

Original lambda:
1.94531s


Function via template copy:
0.094748

### Lambda via template copy:
0.0264867s


Function via template reference:
0.0892594s

### Lambda via template reference:
0.0264201s


Function via std::function reference:
0.0891776s

Lambda via std::function reference:
0.09s

【讨论】:

  • 一本有趣的书!我只是想知道,你为什么会通过模板副本建议 lambda?我的意思是,参考有什么问题?添加副本有什么好处吗?
  • 我不建议复制参考 :) 我说的是所有这些选项。也许不是很清楚。而且您无法确定哪一个(通过引用或通过复制传递 lambda)更好。根据 lambda 的内容(它的大小),它的复制复杂性,通过引用传递可能会也可能不会更快。与按值传递的对象相比,编译器可能更难对引用进行推理。
  • 也许是因为我的母语不是英语,但这句话让我很困惑你可以实际上做同样的事情(重点是我的) . “实际上”这个词让我认为你的意思是这更好......谢谢你的澄清。
  • 你可能已经猜到了,我不是一个以英语为母语的人)我更新了通过副本的措辞,希望这会更好一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-05
  • 2012-04-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多