【问题标题】:Overhead of recursive lambdas递归 lambda 的开销
【发布时间】:2013-06-13 16:02:44
【问题描述】:

与常规递归函数相比,递归 lambda 函数是否会产生任何开销(因为我们必须将它们捕获到 std::function 中)?
此功能与仅使用常规功能的类似功能有什么区别?

int main(int argc, const char *argv[])
{
    std::function<void (int)> helloworld = [&helloworld](int count) {
        std::cout << "Hello world" << std::endl;
        if (count > 1) helloworld(--count);
    }; 
    helloworld(2);
    return 0; 
}

【问题讨论】:

  • 您的主要问题将是编译器优化较少ideone.com/QsZVfH
  • 不错!你知道为什么吗?编译器不能只是猜测这段代码类似于常规函数调用(没有捕获但它本身)?
  • @fscan 我必须这样做,因为它是一个递归 lambda 函数。类型推断在这里不起作用
  • 对不起,不知道这个特殊情况,将删除评论。
  • 我看到一个 lambda 闭包,它通过引用调用 std::function,而不是递归 lambda。然后由闭包分配std::functionstd::function 不是 lambda 闭包,lambda 闭包也不是 std::function。 Lambda 闭包可以存储在std::functions 中,闭包可以通过引用或值捕获std::functions。 lambda 闭包实际上递归调用自身(而不是通过某些中介)的能力目前在 C++11 中不可用......从某种意义上说,这比递归 lambda 更好地描述为“递归std::function” .

标签: recursion c++11 lambda


【解决方案1】:

通过将 lambdas 递归存储为 std::function 存在开销,尽管它们本身基本上是函子。看来gcc 优化不好,直接comparison 可以看出。

实现 lambda 的行为,即创建一个仿函数,使gcc 能够再次优化。您的 lambda 的具体示例可以实现为

struct HelloWorldFunctor
{
   void operator()(int count) const
   {
      std::cout << "Hello world" << std::endl;
      if ( count > 1 )
      {
         this->operator()(count - 1);
      }
   }
};
int main()
{
   HelloWorldFunctor functor;
   functor(2);
}

对于我创建的例子,函子看起来像这个second demo

即使引入了对诸如std::rand 等不纯函数的调用,没有递归 lambda 或使用自定义函子的性能仍然更好。这是third demo

结论:使用std::function 会产生开销,尽管根据用例的不同可能可以忽略不计。由于这种用法会阻止某些编译器优化,因此不应广泛使用。

【讨论】:

  • 这是真的,感谢代码,尽管我怀疑它是非常特定于编译器的。有没有办法在 ideone 上传递编译器标志?
  • @Pedrom 我不这么认为。也许如果你注册?免责声明:我还没有大量使用 lambda,因为我从事的项目需要与不支持 lambda 的编译器向后兼容。
  • 我正在尝试在我的机器上验证这一点,但由于某种原因,它没有使用 cygwin 显示任何输出:/
  • @Pedrom 根本没有输出?这很奇怪。尝试将"\n" 替换为std::endl
  • 不是 lambda 会产生开销。我再说一遍,它是 不是 lambda。它是std::function,它使用类型擦除来存储 lambda。
【解决方案2】:

所以std::function 是多态实现的。这意味着您的代码大致相当于:

struct B
{
    virtual void do(int count) = 0;
};

struct D
{
    B* b;
    virtual void do(int count)
    {
        if (count > 1)
            b->do(count--);
    }
};

int main()
{
    D d;
    d.b = &d;
    d.do(10);
}

很少有足够紧密的递归,以至于虚拟方法查找是一个很大的开销,但根据您的应用程序领域,这当然是可能的。

【讨论】:

    【解决方案3】:

    C++ 中的 Lambda 等效于函子,因此它与调用编译器自动创建的某个类的 operator() 相同。当您捕获环境时,幕后发生的事情是那些捕获的变量被传递给类的构造函数并存储为成员变量。

    所以简而言之,性能差异应该非常接近于零。

    这里有一些进一步的解释:

    跳转到“如何实现 Lambda 闭包?”部分 http://www.cprogramming.com/c++11/c++11-lambda-closures.html

    编辑:

    经过一些研究并感谢 Stefan 的回答和代码,事实证明,由于 std::function 惯用语,recursive lambdas 存在开销。由于 lambda 必须包装在 std::function 上才能调用自身,因此它涉及调用虚函数,这确实会增加开销。

    主题在这个答案的 cmets 上被处理: https://stackoverflow.com/a/14591727/1112142

    【讨论】:

    • Stephan Lavavej 提出了类似的想法here(0:38:20)。因此,调用常规函数似乎会减少开销。但是我记得在某处读到过,在某些情况下,这种仿函数方法可以使用函数指针进行优化。真的吗 ?我提供的案例呢?
    • @3XX0 这是一个不错的视频,感谢您的链接 :) 但是他说,如果您打算一遍又一遍地使用它,那么编写一个常规函数是有意义的,但这只是因为它是更容易在代码的不同部分调用,他不是在谈论性能。关于函数指针,您可以使用它们而不是 lambda,但我认为它们不会那么方便,实际上我没有看到任何性能差异。
    • @3XX0 至于你的例子,它就像调用一个函数 void hello_world(myfunc foo, int count) 如果有 any 开销它会在第一次调用(因为您需要实例化一个对象 foo 将其传递给该方法,但之后所有以下调用在性能方面完全相同。此外,如果您的递归函数是一个方法,那么根本没有区别。
    • @3XX0 我在考虑你问的优化,是的,可以稍微优化调用以避免绑定到 std::function 变量,但我认为在这种情况下会调用普通函数更有意义,因为 lambda 版本将失去其可读性优势。
    猜你喜欢
    • 1970-01-01
    • 2011-04-29
    • 1970-01-01
    • 2010-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多