67

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

例如

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

还是由于缺乏优化而导致效率损失?

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

4

3 回答 3

48

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

为什么编译器可以比普通函数更好地优化 lambda?

正如其他人已经指出的那样,不能保证你的调用会被内联,但你有更好的机会使用 lambdas。检查调用是否已内联的一种方法是检查生成的代码。如果您使用 gcc,请将 -S 标志传递给编译器。当然,它假设您可以理解汇编代码。



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

海合会-Winline

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

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

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

他指出的另一面旗帜是:

-Rpass=inline

发出优化报告的选项

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

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

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

于 2013-04-10T16:54:58.813 回答
44

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

但是这里有一个概念混淆:在 C++ 标准中,“内联”是函数的链接,即它是关于如何定义函数的声明,而不是如何调用它。内联定义的函数可以受益于编译器优化,通过这种优化可以内联对此类函数的调用。这是一个不同但高度相关的概念。

在 lambda 的情况下,被调用的实际函数operator()是隐式定义为inline编译器为 lambda 创建的匿名类中的成员。对 lambda 的调用被转换为对其的直接调用,operator()因此可以内联。我在另一个答案中更详细地解释了编译器如何创建 lambda 类型

于 2013-04-10T17:21:25.170 回答
15

这取决于给予编译器的优化级别。以这两个功能为例,它们在语义上是相同的。一种是 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 调用也不是)。

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

于 2013-09-13T12:09:14.013 回答