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

时间:2012-12-05 11:38:03

标签: c++ optimization c++11 lambda compiler-optimization

在他的书The C++ Standard Library (Second Edition)中,Nicolai Josuttis指出,编译器可以比普通函数更好地优化lambdas。

  

此外,C ++编译器比它们更好地优化lambdas   普通功能。   (第213页)

为什么?

我认为在内联时不应该有任何区别。我能想到的唯一原因是编译器可能有一个更好的本地上下文与lambdas,这样可以做出更多假设并执行更多优化。

3 个答案:

答案 0 :(得分:163)

原因是lambda是函数对象,因此将它们传递给函数模板将实例化一个专门用于该对象的新函数。因此编译器可以简单地内联lambda调用。

另一方面,对于函数,旧的警告适用:函数指针被传递给函数模板,并且编译器传统上在通过函数指针调用调用方面存在很多问题。理论上它们可以内联,但前提是内联函数也是内联的。

例如,请考虑以下函数模板:

template <typename Iter, typename F>
void map(Iter begin, Iter end, F f) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

用这样的lambda调用它:

int a[] = { 1, 2, 3, 4 };
map(begin(a), end(a), [](int n) { return n * 2; });

此实例化中的结果(由编译器创建):

template <>
void map<int*, _some_lambda_type>(int* begin, int* end, _some_lambda_type f) {
    for (; begin != end; ++begin)
        *begin = f.operator()(*begin);
}

...编译器知道_some_lambda_type::operator ()并且可以简单地内联调用它。 (并且使用任何其他lambda调用函数map将创建map的新实例化,因为每个lambda具有不同的类型。)

但是当使用函数指针调用时,实例化如下所示:

template <>
void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

...此处f指向每个map调用的不同地址,因此编译器无法内联对f的调用,除非对map的周围调用也有内联,以便编译器可以将f解析为一个特定的函数。

答案 1 :(得分:24)

因为当你将“函数”传递给算法时,你实际上是在传递一个指向函数的指针,所以它必须通过指向函数的指针进行间接调用。当您使用lambda时,您将对象传递给专门为该类型实例化的模板实例,并且对lambda函数的调用是直接调用,而不是通过函数指针调用,因此更可能内联。

答案 2 :(得分:-1)

Lambda 不会比通常的函数更快或更慢。 如有错误请指正。

首先,lambda 和普通函数有什么区别:

  1. Lambda 可以捕获。
  2. 极有可能的 Lambda 在从目标文件编译期间可以简单地移除,因为它具有内部链接。

让我们谈谈捕获。它不会为函数提供任何性能,因为编译器必须传递带有处理捕获所需的数据的附加对象。无论如何,如果您只是在适当的位置使用 lambda 函数,它将很容易优化。此外,如果 lambda 不使用捕获,您可以将 lambda 转换为函数指针。为什么?因为如果没有捕获就只是一个普通的函数。

void (*a1)() = []() {
    // ...
};
void _tmp() {
    // ...
}
void (*a2)() = _tmp;

以上两个例子都是有效的。

说到从目标文件中删除函数。您可以简单地将您的函数放入匿名命名空间,它就会达成交易。函数内联会更开心一点,因为它不会在除了文件之外的任何地方使用。

auto a1 = []() {
    // ...
};

namespace {
    auto a2() {
        // ...
    }
}

上述功能将与性能相同。

我还注意到函数指针和 lambdas 进行了比较。这不是一件好事,因为它们是不同的。当你有一个指向函数的指针时,它可以指向各种不同的函数,并且可以在运行时改变,因为它只是一个指向内存的指针。 Lambda 不能这样做。它总是只用一个函数运行,因为调用哪个函数的信息存储在类型本身中。

你可以用这样的函数指针编写代码:

void f1() {
    // ...
}
void f2() {
    // ...
}
int main() {
    void (*a)();
    a = f1;
    a = f2;
}

完全没问题。并且您不能以这种方式使用 lambda 编写代码:

int main() {
    auto f1 = []() {
        // ...
    };
    auto f2 = []() {
        // ...
    };
    f2 = f1; // error: no viable overloaded '='
}

如果某些库接受函数指针,并不意味着编译器可以比普通函数更好地优化 lambda,因为问题不在于公共库和函数指针。

相关问题