【问题标题】:"Lambdas are cheap" - Really? Under what circumstances?“Lambdas 很便宜”——真的吗?在什么情况下?
【发布时间】:2020-12-15 05:53:52
【问题描述】:

这篇文章声明使用 C++ Lambda 是“便宜的”: Lambdas aren't magic - part 2

他们演示了如何将 lambdas 传递给现有的 std 函数/模板。 一篇文章演示了如何在不使用 std::function 的情况下使用“auto”作为函数的返回类型来返回 lambda。

我看到的任何文章都没有演示如何制作自己的函数,尤其是。类成员函数,它采用 lambda 或更多,不使用 std::function。

所以这个“lambda 很便宜”的大胆声明 - 在现实世界的场景中真的是真的吗?

作为参考:对于我来说,“便宜”是为了解决这个问题:在具有几百 KB 内存和两位数 MHz 速度的嵌入式裸机项目上非常有用。 (我一直在该领域使用 C++ 的一个健全的子集,并且正在寻找我可以使用的其他东西)

据我所见,std::function 并不便宜。一方面,作为 std::function 传递的 Lambda 显然无法再进行内联优化。 但更糟糕的是,一个 std::function 是 32 字节大。 同样显然,如果捕获的内容超出了范围,是否可以使用动态分配? 这一切听起来都是个坏消息。

因此,当我在寻找在没有 std::function 的情况下使用 lambda 的方法时,只找到了一个返回 auto 的示例,我尝试了这个: 我制作了一个非常简单的类,它在成员函数中使用“auto”作为参数类型,编译器似乎对此很满意(尽管就预期的仿函数参数而言,它不像 std::function 那样“自我记录”代码)。

struct FuncyClass
{   unsigned func(auto fnx)
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    auto result = fc.func( [](auto x){return x*3;} );
    
    printf("Result: %u\n", result);
    return 0;
}
// Output: "Result: 42"

但我有一种感觉,当它被更复杂的场景使用时,这个非常简单的场景并没有向我展示可能出现的编译器错误泛滥。 我不太了解这种语法在幕后发生了什么,编译器在确定时会做什么,从仿函数的使用,期望什么参数和返回类型,以及这个带有自动参数的函数是如何在幕后实现的.

that,即使用自动类型化的 args,真的是让你的类成员函数 lambda 可定制的明智方法吗? 然后它看起来“便宜”,因为在我的测试中,使用 auto 而不是 std::function 时可执行文件要小得多。

当然,它仍然是有限的: 如果不使用 std::function (或类似类型的 DIY 包装器),类就无法保留 lambda,对吗? 是否有可能防止动态分配发生,例如当出现需要 std::function 分配内存的情况时使其成为编译时错误?

【问题讨论】:

  • Lambda 本身很便宜。 std::function 不是,但您不需要使用它。非捕获 lambda 可以转换为函数指针,捕获 lambda 可以作为模板传递。
  • std::function 很昂贵,因为它使用类型擦除。一个 lambda 只是一个简写函子,相比之下那些是“便宜的”。您可以将您的类设为模板并为其指定 lambda 类型以避免使用 std::function
  • "不需要使用 std::function" - 是的,但不幸的是,正如我读到的 10 篇文章/博客文章中显示的那样,这是“这样做的方法” - 所以我的印象是这是在您自己的类中有效地使用 lambda 的方法。
  • 几乎所有采用函数/函子参数的标准库函数(例如std::sort 等)都无需使用std::function。他们使用模板。
  • 这就是权衡。如果您知道类型,则可以进行大量优化,但由于您必须处理类型,因此会使代码更加复杂。如果你去“无类型”(std::function),那么代码更容易使用,但你会付出性能损失。

标签: c++ lambda c++14


【解决方案1】:

Lambda 只是重载operator() 的对象。您可以在概念上将它们视为等同于:

class Lamba {
public:
    auto operator()(...) const { /* ... */ }
};

因此,它们并不比类似的函数调用更昂贵。 std::function 不需要使用 lambda。您可以使用类型推导来存储/传递它们:

template <typename Func>
void foo(Func&& func) { /* ... */ }

对于非捕获 lambda,您可以使用 operator+ 将它们隐式或显式转换为函数指针:

void (*fp)(int) = [](int){ /* ... */ };

您的问题难以回答的原因是您在问 lambdas 是否昂贵。但是,与什么相比呢?如果您想知道它们在您的特定情况下是否具有足够的性能,您将不得不自己进行一些分析并弄清楚。

【讨论】:

  • “与什么相比”——不过,我给出了一个粗略的参考标记。有更广泛、更实用的看待事物的方法,有些事情有时可以被归类为整个类别的场景成本过高,而无需以纳秒为单位进行衡量多么它实际上有多糟糕,我怀疑这可能是这样的情况。您不会坚持用零漂移微伏表测量是否更换电池。
【解决方案2】:

std::function 很好,因为它具有简单的语法(比函数指针容易得多)并且它可以自行记录所需的函数类型(与需要显式文档的模板相比)。我想有关 lambdas 的介绍性文章会使用它,因为在您有某些限制之前,成本是不可见的。

接受 lambda 作为参数还有另外两种方法:函数指针和模板。

非捕获 lambda 可以隐式转换为函数指针:

struct FuncyClass
{   unsigned func(int(*fnx)(int)) 
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    auto result = fc.func( [](auto x){return x*3;} );
    
    printf("Result: %u\n", result);
    return 0;
}

可以在模板的帮助下传递捕获和非捕获 lambda:

struct FuncyClass
{   
    template<typename Func>
    unsigned func(Func&& fnx)
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    int multiplier = 3;
    auto result = fc.func( [multiplier](auto x){return x*multiplier;} );
    
    printf("Result: %u\n", result);
    return 0;
}

【讨论】:

  • 据说can be passed as template 但实际上您并没有将任何内容传递给模板定义,而是让编译器从 lamda 推断替换。这可能会令人困惑
【解决方案3】:

“Lambda 很便宜”

这是一个相对的概念。

实际上它在很大程度上取决于您的 C++ 编译器。

尝试使用最近的 GCC(在 2020 年,使用 GCC 10)和 enable 警告和优化,因此在命令行上编译您的 C++ 代码至少使用 g++ -Wall -Wextra -O2

然后对您的应用程序进行基准测试或profile

在 Linux 上,请参阅 time(7) 并考虑使用 gprof(1)perf(1)(当然,一旦您使用 GDB 调试过您的程序)。对于其他操作系统和编译器,找到一些等效的。

这份draft 报告可能会建议您optimizations 一个好的编译器可以做什么(有时不这样做,因为Rice's theorem)。 有时可能会发生 lambda 应用程序变为inlined

如果您喜欢 functional programming 范例,也可以考虑使用 OcamlCommon Lisp (SBCL) 或 Haskell。您会发现在实践中可能比 C++(GCCClang)稍快一些的情况,尤其是在 Linux 上用于单线程程序。

根据经验,我倾向于认为只要 C++ lambda 的主体在做一些重要的工作,比如在某些 C++ container 中迭代(或搜索),它就会很便宜。如果 lambda 主体只是进行整数加法,则开销很大(除非编译器足够聪明地内联它)。如果它在具有数千个条目的std::map 上执行一些find operation,或者实际上使用动态分配(所以一些new,经常使用malloc)通常是无关紧要的。 p>

实际上,您需要从 YMMV 开始分析您的应用程序。

【讨论】:

  • 考虑到我得到的最初印象是 std::function 很难避免与 lambda 一起使用,而且我估计大多数人认为“动态分配可以发生”是“不便宜”,它对我来说似乎并没有那么模糊。基本上,我希望的“是的,它真的很便宜”“证明”是证明使用没有 std::function 的 lambdas 既可行又实用——我希望这种情绪能在我的帖子中体现出来。
  • @sktpin:如果您的问题是真的关于是否可以使用 lambdas 而无需在std::function(或类似的基于分配的操作)中捕获它们,那么也许您应该相应地调整你的标题和问题文本,而不是使用关于什么是“便宜”等问题。
  • 问题背后的动机是我想知道 lambdas 是否可以“便宜”地实际使用。 看起来的东西是std::function,因此是焦点。不过,我对它的理解可能是错误的,而且我可能还缺少更多方面。因此,仅仅询问 std::function 可能不会给出做出实际决策实际需要的全貌。为什么对 SO 的思考使得只有亚原子级别的问题才被认为是好的?似乎您基本上可以自己回答您的问题。
猜你喜欢
  • 2021-12-19
  • 1970-01-01
  • 2011-08-28
  • 2011-03-22
  • 1970-01-01
  • 2011-06-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多