最近使用lambda进行替换原有逻辑,不经想到使用lambda与直接写逻辑究竟有什么区别。当然在不开优化下lambda会变成匿名函数,但是在开了O2优化的情况是否还是一样呢?是否lambda也是会进行内联。
本文各种汇编代码皆是在开启O2优化的情况下生成,使用的是VS2019。
最简单的labmda
首先我我们先看一下最简单lambda。
// main.cpp
int main()
{
using namespace std;
auto testf = []() {
for (int i = 0; i < 10; ++i)
{
if (i % 2 == 0)
cout << "hi";
}
};
testf();
}
查看反汇编代码,这里是testf()的反汇编
00007FF6DB7E1246 xor ebx,ebx
00007FF6DB7E1248 test bl,1
00007FF6DB7E124B jne main+20h (07FF6DB7E1260h)
00007FF6DB7E124D mov rcx,qword ptr [__imp_std::cout (07FF6DB7E20B0h)]
00007FF6DB7E1254 lea rdx,[string "hi" (07FF6DB7E2284h)]
00007FF6DB7E125B call std::operator<<<std::char_traits<char> > (07FF6DB7E1000h)
00007FF6DB7E1260 inc ebx
00007FF6DB7E1262 cmp ebx,0Ah
00007FF6DB7E1265 jl main+8h (07FF6DB7E1248h)
发现在O2的优化下直接就内联了,也没有产生函数调用,第6行的call是调用cout的。
类中的lambda
类A的声明与定义
// A.h
#pragma once
#include <iostream>
#include<string>
//#define VIRTUAL
//#define NOINLINE
#ifdef VIRTUAL
class Base {
public:
virtual void f(){
std::cout << "Base";
};
};
#endif
class A
#ifdef VIRTUAL
: public Base
#endif
{
public:
A(int a, std::string b) :parm1(a), parm2(b) {
};
A(int a) :parm1(a) {
};
#ifdef NOINLINE
__declspec(noinline)
#endif
#ifdef VIRTUAL
virtual
#endif
void f();
int parm1;
std::string parm2;
};
f函数的定义
// A.cpp
#include"A.h"
void A::f()
{
int s_t = 1;
auto t = [&s_t, this]() {
for (int i = 0; i < 10; ++i)
{
if (i % 2 == 0)
std::cout << "hi";
}
parm1 += parm2.size();
#ifdef VIRTUAL
Base::f();
#else
f();
#endif
};
t();
}
main文件
//main.cpp
#include "A.h"
extern class Base;
extern class A;
int main()
{
using namespace std;
A a(1, "hi");
#ifdef VIRTUAL
a.f();
Base* t = &a;
t->f();
std::cout << a.parm1 << a.parm2;
#else
a.f();
#endif // VURTUAL
}
VIRTUAL和ONINLINE使用宏控制使用虚函数时的情况与强制直接内联的情况。
首先我们不定义ONINLINE和VIRTUAL宏,来看一下a.f()的反汇编代码
00007FF7283E1557 test bl,1
00007FF7283E155A jne main+178h (07FF7283E1568h)
00007FF7283E155C mov rcx,qword ptr [__imp_std::cout (07FF7283E3080h)]
00007FF7283E1563 call std::operator<<<std::char_traits<char> > (07FF7283E1000h)
00007FF7283E1568 inc ebx
00007FF7283E156A cmp ebx,0Ah
00007FF7283E156D jl main+167h (07FF7283E1557h)
可以发现f()函数直接就被优化了,函数调用都被优化没了。这样子我我们无法测试类内函数使用lambda的情况,所以我定义对了ONINLINE宏,使编译器强制不内联f()函数。
让我们看看定义了ONINLINE的反汇编吧。
00007FF7B39C1599 lea rcx,[rbp-38h]
00007FF7B39C159D call A::f (07FF7B39C1000h)
00007FF7B39C15A2 nop
可以看到确实不内联了,我们进函数内看一看
void A::f()
{
00007FF7B39C1000 mov qword ptr [rsp+8],rbx
00007FF7B39C1005 push rdi
00007FF7B39C1006 sub rsp,20h
00007FF7B39C100A mov rdi,rcx
int s_t = 1;
auto t = [&s_t, this]() {
for (int i = 0; i < 10; ++i)
{
if (i % 2 == 0)
std::cout << "hi";
}
parm1 += parm2.size();
#ifdef VIRTUAL
Base::f();
#else
//f();
#endif
};
00007FF7B39C100D xor ebx,ebx
00007FF7B39C100F nop
t();
00007FF7B39C1010 test bl,1
00007FF7B39C1013 jne A::f+21h (07FF7B39C1021h)
00007FF7B39C1015 mov rcx,qword ptr [__imp_std::cout (07FF7B39C3080h)]
00007FF7B39C101C call std::operator<<<std::char_traits<char> > (07FF7B39C1040h)
00007FF7B39C1021 inc ebx
00007FF7B39C1023 cmp ebx,0Ah
00007FF7B39C1026 jl A::f+10h (07FF7B39C1010h)
00007FF7B39C1028 mov eax,dword ptr [rdi+18h]
00007FF7B39C102B add dword ptr [rdi],eax
}
可以发现lambda又被优化掉了。
虚函数中的lambda
接着我们定义VIRTUAL宏,不定义NOINLINE宏,反汇编看一下
#ifdef VIRTUAL
a.f();
00007FF6C5EB15D4 test bl,1
00007FF6C5EB15D7 jne main+18Ch (07FF6C5EB15ECh)
00007FF6C5EB15D9 lea rdx,[string "hi" (07FF6C5EB3358h)]
00007FF6C5EB15E0 mov rcx,qword ptr [__imp_std::cout (07FF6C5EB3088h)]
00007FF6C5EB15E7 call std::operator<<<std::char_traits<char> > (07FF6C5EB1060h)
00007FF6C5EB15EC inc ebx
00007FF6C5EB15EE cmp ebx,0Ah
00007FF6C5EB15F1 jl main+174h (07FF6C5EB15D4h)
00007FF6C5EB15F3 mov eax,dword ptr [rbp-18h]
00007FF6C5EB15F6 add dword ptr [rbp-30h],eax
00007FF6C5EB15F9 lea rdx,[string "Base" (07FF6C5EB3350h)]
00007FF6C5EB1600 mov rcx,qword ptr [__imp_std::cout (07FF6C5EB3088h)]
00007FF6C5EB1607 call std::operator<<<std::char_traits<char> > (07FF6C5EB1060h)
Base* t = &a;
t->f();
00007FF6C5EB160C lea rcx,[rbp-38h]
00007FF6C5EB1610 mov rax,qword ptr [rbp-38h]
00007FF6C5EB1614 call qword ptr [rax]
可以发现a.f()这种自身调用直接内联优化了,而多态转换后的无法内联优化,我们进去看一下lambda是否还会被内联优化。
void A::f()
{
00007FF6C5EB1000 mov qword ptr [rsp+8],rbx
00007FF6C5EB1005 push rdi
00007FF6C5EB1006 sub rsp,20h
00007FF6C5EB100A mov rdi,rcx
int s_t = 1;
auto t = [&s_t, this]() {
for (int i = 0; i < 10; ++i)
{
if (i % 2 == 0)
std::cout << "hi";
}
parm1 += parm2.size();
#ifdef VIRTUAL
Base::f();
#else
//f();
#endif
};
00007FF6C5EB100D xor ebx,ebx
00007FF6C5EB100F nop
t();
00007FF6C5EB1010 test bl,1
00007FF6C5EB1013 jne A::f+28h (07FF6C5EB1028h)
00007FF6C5EB1015 mov rcx,qword ptr [__imp_std::cout (07FF6C5EB3088h)]
00007FF6C5EB101C lea rdx,[string "hi" (07FF6C5EB3358h)]
00007FF6C5EB1023 call std::operator<<<std::char_traits<char> > (07FF6C5EB1060h)
00007FF6C5EB1028 inc ebx
00007FF6C5EB102A cmp ebx,0Ah
00007FF6C5EB102D jl A::f+10h (07FF6C5EB1010h)
00007FF6C5EB102F mov eax,dword ptr [rdi+20h]
00007FF6C5EB1032 lea rdx,[string "Base" (07FF6C5EB3350h)]
00007FF6C5EB1039 add dword ptr [rdi+8],eax
00007FF6C5EB103C mov rcx,qword ptr [__imp_std::cout (07FF6C5EB3088h)]
}
可以看到不仅仅时lambda被优化,甚至父类函数也被优化了。
总结
可以发现在不涉及多态的情况下,编译器能在编译期就能确定具体的调用函数,那么这个函数就可能会被内联。就算是lambda也不例外。
所以一般情况下使用lambda的开销几乎为0,因为编译器会做内联优化。