Why can lambdas be better optimized by the compiler than plain functions?
在他的书中,The C++ Standard Library (Second Edition)Nicolai Josuttis指出,与普通函数相比,编译器可以更好地优化lambdas。
In addition, C++ compilers optimize lambdas better than they do
ordinary functions.
(Page 213)
为什么会这样?
我想,当谈到内衬时,不应该再有任何区别了。我能想到的唯一原因是编译器可能与lambda具有更好的本地上下文,这样可以做出更多假设并执行更多优化。
- 相关的。
- 基本上,该语句适用于所有函数对象,而不仅仅是lambda。
- 这是不正确的,因为函数指针也是函数对象。
- @利特布:我想我不同意这一点。^ ^ ^ ^ ^ ^ ^ ^ w w(w w ^ ^ ^ ^ ^ w w)(在查看标准之后),我不知道C++的ISM,虽然我认为在共同的说法(和根据维基百科),人们指的是一些可调用类当他们说函数对象时的实例。
- 有些编译器可以比普通函数更好地优化lambda,但并非全部:-(
原因是lambda是函数对象,因此将它们传递给函数模板将为该对象实例化一个新函数。因此,编译器可以非常简单地内联lambda调用。
另一方面,对于函数,旧的警告适用:函数指针被传递到函数模板,而编译器传统上通过函数指针在内联调用时有很多问题。它们理论上是内联的,但前提是周围的函数也是内联的。
例如,考虑以下函数模板:
1 2 3 4 5
| template <typename Iter, typename F>
void map(Iter begin, Iter end, F f) {
for (; begin != end; ++begin)
*begin = f(*begin);
} |
用这样的lambda调用它:
1 2
| int a[] = { 1, 2, 3, 4 };
map(begin(a), end(a), [](int n) { return n * 2; }); |
此实例化的结果(由编译器创建):
1 2 3 4 5
| 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都有不同的类型。)
但是当使用函数指针调用时,实例化看起来如下:
1 2 3 4 5
| 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解析为一个特定的函数。
- 也许值得一提的是,用不同的lambda表达式实例化相同的函数模板将创建一个具有唯一类型的全新函数,这很可能是一个缺点。
- fwiw:如果传递给map(通过函数指针)的函数很小,并且定义在同一个源文件中,那么当循环展开时,它很可能是内联的。
- @格雷戈当然。问题在于处理不能内联的函数时(因为它们太大)。在这里,对回调的调用在lambda的情况下仍然可以内联,但在函数指针的情况下不能内联。std::sort是使用lambda而不是函数指针的经典例子,这里最多有七倍(可能更多,但我没有关于它的数据!)性能提高。
- @Konradrudolph我明白这一点,但是如果函数太大或太复杂以至于编译器无法将其内联,那么您可能不会在lambda中编写它,因此比较不太合适。除非我遗漏了一些东西——在lambda强制内联中执行大型操作吗?因为在我看来,编译器仍然可以选择为lambda生成函数代码,而不是内联(如果它是一个大操作),并且在使用前转换为函数指针。显然,lambda似乎是确保简单操作是内联的一种好方法。
- @Greggo你在这里混淆了两个函数:我们传递lambda到的函数(例如,在我的例子中,std::sort或map)和lambda本身。lambda通常很小。另一个功能——不一定。我们关注的是在另一个函数内部对lambda的内联调用。
- @Konradrudolph澄清:我的意思是:如果您在文件范围内编写int dblit(int x){return x*2;},并在示例中将"&dblit"作为最后一个parm to map(),它可以并且很可能以与lambda相同的方式在map的扩展中内联。取决于优化选项。
- @格雷戈,我知道。不过,这正是我回答的最后一句话所说的。
- 我觉得奇怪的是,给定一个简单的布尔函数pred,它的定义是可见的,使用gcc v5.3,std::find_if(b, e, pred)不内联pred,但std::find_if(b, e, [](int x){return pred(x);})是可见的。Clang设法同时内联这两种代码,但使用lambda生成代码的速度不如G++快。
因为当你将一个"函数"传递给一个算法时,你实际上是在传递一个指向函数的指针,所以它必须通过指向函数的指针进行间接调用。当使用lambda时,正在将对象传递给为该类型专门实例化的模板实例,而对lambda函数的调用是直接调用,而不是通过函数指针进行的调用,因此更可能是内联的。
- "对lambda函数的调用是直接调用"-实际上。同样的事情也适用于所有的函数对象,而不仅仅是lambda。它只是函数指针,如果可以的话,就不能那么容易地进行内联。