lambda 是不是像 C++ 中的函数一样内联?

Posted

技术标签:

【中文标题】lambda 是不是像 C++ 中的函数一样内联?【英文标题】:Are lambdas inlined like functions in C++?lambda 是否像 C++ 中的函数一样内联? 【发布时间】:2013-04-02 14:00:58 【问题描述】:

编译器可以/是否内联 lambda 函数来提高效率,就像使用简单的标准函数一样?

例如

std::vector<double> vd;
std::for_each(vd.begin(), vd.end(), [](const double d) return d*d;);

还是没有优化导致效率下降?

第二个问题:我在哪里可以检查我使用的编译器是否优化了内联函数的调用,这些调用被发送到算法?我的意思是,如果一个函数(而不是函数对象)被发送到一个算法,最后一个会得到一个指向该函数的指针,并且一些编译器会优化指向内联函数的指针,而另一些则不会。

【问题讨论】:

有些是优化的,有些不是,就像任何函数调用一样。如果您对特定调用感兴趣,则需要检查特定编译器对该特定调用的作用。 你在这里混淆了概念。所有 lambdas 都是内联的。并非所有对它们的调用都必须内联。 如果将 lambda 传递给外部函数,我认为它不能被内联。 Konrad Rudolph@ 不要混淆 ppl,code inline 和 code inplace 是不一样的。所有 lambdas 绝对不是内联的。但是所有的 lambdas 肯定是就地的。 inline 是为编译器特定的优化保留的,它与像 lambdas 那样的就地编码没有太大的相关性。 @Andry 你完全搞反了。请阅读下面的答案以获得正确的解释。 【参考方案1】:

在简单的情况下,例如您的示例,您应该期望使用 lambdas 比使用函数指针有更好的性能,请参阅

Why can lambdas be better optimized by the compiler than plain functions?

正如其他人已经指出的那样,不能保证您的调用会被内联,但使用 lambda 的机会更大。检查调用是否已内联的一种方法是检查生成的代码。如果您使用 gcc,请将 -S 标志传递给编译器。当然,前提是你能看懂汇编代码。



2018 年 9 月 11 日更新:Vipul Kumar 在他的编辑中指出了两个编译器标志。

GCC -Winline

如果声明为 inline 的函数不能被内联,则发出警告。即使使用此选项,编译器也不会警告系统头文件中声明的内联函数失败。

编译器使用各种启发式方法来确定是否内联函数。例如,编译器会考虑被内联的函数的大小以及在当前函数中已经完成的内联量。因此,源程序中看似微不足道的变化,都会导致-Winline产生的警告出现或消失。

据我了解,如果您的函数未内联声明,则此编译器标志很可能没有有用。不过很高兴知道它的存在,它部分回答了您的第二个问题。

他指出的另一个标志是:

叮当-Rpass=inline

发送优化报告的选项

优化报告从高层次上跟踪所有主要决策 由编译器转换完成。例如,当内联 决定将函数 foo() 内联到 bar() [...]

我自己没有使用过这个,但根据文档,它可能对您的用例有用。

每当生成的程序集非常重要时,我都会亲自检查它。

【讨论】:

【参考方案2】:

首先:C++ 中 lambda 设计的重点在于,与函数调用相比,它们没有开销。这尤其包括可以内联对它们的调用这一事实。

但是这里有一个概念混淆:在 C++ 标准中,“inline” is the linkage of a function, i.e. it is a statement about how a function is defined,而不是它的调用方式。内联定义的函数可以受益于编译器优化,通过内联对此类函数的调用。这是一个不同但高度相关的概念。

对于 lambda,实际调用的函数是成员 operator(),在编译器为 lambda 创建的匿名类中隐式定义为 inline。对 lambda 的调用被转换为对其operator() 的直接调用,因此可以内联。 I’ve explained how the compiler creates lambda types in more detail in another answer.

【讨论】:

有趣的是,这是我最不赞成的答案之一,但没有人解释它有什么问题。 我认为这应该在最上面,事实上它被称为成员 operator() 使得设置对内联的期望非常容易。 确实,很有趣的是,被接受且最受好评的答案实际上是指您写的答案,而您在此处的答案(与链接的答案相似)被低估了。其实我认为你的(选择哪个:-P)更重要...... 足够简单,C++ 中的 lambdas 应该有开销,否则它们中的所有内容如何隐藏到简单的operator()?你错过了让人们知道什么是错误的选择的想法。 lambdas 的想法不在开销中,想法是将代码作为数据放在之前无法发送的地方,然后按需调用它。就是这样。 @Andry 同样,您不知道自己在说什么,尤其是您的第一条评论毫无意义。您对 C++(尤其是链接)的基本事实是错误的,并且在您了解有关该语言的基础知识和编译器/链接器架构的更多信息之前,您将无法理解 lambda 在 C++ 中的工作原理。 【参考方案3】:

这取决于给予编译器的优化级别。以这两个功能为例,它们在语义上是相同的。一种是C++11风格,另一种是C风格。

void foo1 (void)

    int arr[100];
    std::generate(std::begin(arr), std::end(arr), []()return std::rand()%100;);


void foo2 (void)

    int arr[100];
    for (int *i = arr; i < arr+100; i++) *i = std::rand()%100;

使用 gcc -O4 编译它会生成两个函数极其相似(不相同,但复杂度相当)的代码。

但是,在编译未优化时,lambda 没有内联(std::begin 和 std::end 调用也没有)。

因此,尽管编译器可以(并且确实)在被要求优化现代风格代码方面做得非常出色,但在未优化的调试版本中,这种代码可能会或可能会出现性能损失。

【讨论】:

顺便说一句,gcc 中没有实际的“-O4”。 @DrYak 我总是用“-O9999”编译,因为我还没有找到limit-breaker...当然我在开玩笑。从技术上讲,“-O3”以上的所有内容都转换为“-O3”。 +1 指出了人们不应该发布调试版本这一显而易见但经常被忽视的事实:) 我在职业生涯中经常看到这种情况,甚至被那些人告诉我的代码是“慢”,当然他们确实发布了 DEBUG 版本,哈哈。 @EmilyL。 -O-1 呢?它是否下溢包装到最大值? :) @EmilyL。我认为这是获得 -O-1 的关键,其中 GCC 故意试图减慢代码的速度。你试过 -O[limit-breaker + 1] 吗?它会循环到负值吗?

以上是关于lambda 是不是像 C++ 中的函数一样内联?的主要内容,如果未能解决你的问题,请参考以下文章

使用内联汇编器从 GCC 中的共享库调用函数

函数式编程———内联函数

如何在 C++ 中定义匿名函数?

表值函数是不是会像视图一样被数据库引擎优化?

Kotlin函数 ⑦ ( 内联函数 | Lambda 表达式弊端 | “ 内联 “ 机制避免内存开销 - 将使用 Lambda 表达式作为参数的函数定义为内联函数 | 内联函数本质 - 宏替换 )

c++中的内联函数inline