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


171

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

此外,C ++编译器比常规函数更好地优化了lambda。(第213页)

这是为什么?

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



基本上,该语句适用于所有函数对象,而不仅限于lambda。
newacct 2012年

4
那将是不正确的,因为函数指针也是函数对象。
Johannes Schaub-litb 2012年

2
@litb:我认为我不同意。维基百科),人们说功能对象时就表示可调用类的实例。
塞巴斯蒂安·马赫

1
一些编译器比普通函数可以更好地优化lambda,但不是全部:-(
Cody Gray

Answers:


175

原因是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 ()并且可以轻松地内联对其的调用。(并且map与其他任何 lambda 一起调用该函数都会创建一个新的实例化,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为一个特定的函数。


4
也许值得一提的是,使用不同的lambda表达式实例化相同的函数模板将创建具有唯一类型的全新函数,这很可能是一个缺点。
冷却

2
@greggo绝对。问题在于处理无法内联的函数时(因为它们太大)。在使用lambda的情况下,仍然可以内联对回调的调用,但在使用函数指针的情况下,则不能进行内联。std::sort这是使用lambda而不是函数指针的经典示例,此处性能提高了7倍(可能更多,但我没有数据!)。
康拉德·鲁道夫2014年

1
@greggo您在这里混淆了两个函数:一个是我们将lambda传递(例如std::sortmap在我的示例中)的函数,另一个是lambda本身。λ通常很小。其他功能–不一定。我们关心的是在另一个函数中内联对lambda的调用。
康拉德·鲁道夫2014年

2
@greggo我知道。不过,这实际上就是我回答的最后一句话。
康拉德·鲁道夫2014年

1
我发现很好奇(只是偶然发现)的是,给定一个简单的布尔函数,pred其定义是可见的,并且使用gcc v5.3 std::find_if(b, e, pred)不会内联pred,但是std::find_if(b, e, [](int x){return pred(x);})可以。Clang设法内联两者,但是用lambda产生的代码不如g ++快。
rici

26

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


5
“对lambda函数的调用是直接调用”-的确如此。对于所有函数对象,不仅是lambda ,都是一样。只是函数指针根本无法轻松内联。
皮特·贝克尔,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.