去年设计了一个SDK
,负责处理封装一些事件,对外提供一个类接口,服务初始化的时候,调用方实现对应的类,并将对象指针传给模块。
接触过C11
,好奇心害死猫,就想着这些接口都用lambda
函数对象回调来实现会是什么结果,和纯虚函数的接口定义方法比较,更加灵活。
疑问就出现了,两种不同的语法,从性能角度来说,哪个更快一些?不懂编译原理,弄段代码试试看。
前言
在线网址,能选择不同编译器,编译参数,在linux
平台运行代码,亦或者查看对应的汇编代码。
- https://wandbox.org/:有时候做些技术验证,网页执行小片段的代码很省事
- https://godbolt.org/:用不同的颜色,区分不同的汇编对应的代码,比本地的调试器看起来更加省事。
正文
标准委员会制定了语法的规则,在编译层面,如何实现,取决于各家的编译器,这里不得不说一声,微软的编译器,挺厉害的。语法糖不是万能的,回调接口不多,使用lambda
更加便捷,也无需定义空回调函数接口;回调接口种类繁多的时候,传统的虚函数更有利于业务接口定义的统一。
windows
平台,两者性能接近,没有太多的差异linux
平台,虚函数和lambda
比较,单次多了1.35ns
常规的业务系统开发中,此级别的性能损耗可以忽略,引入lambda
,在设计的上,能带来更多的便捷。在设计多信号处理时,尤为明显,底层有事件触发,如果需要落地日志,出入日志对象的的处理函数。当需要更多的业务处理接口时,底层用vector
保存lambda
对象,事件触发时,依次遍历调用,类似于QT
中的信号和槽,日志、监控、业务1、业务2,互相之间完全解耦。
代码
Counter: 1000000
Time: 3966us
Counter: 1000000
Time: 5316us
#include <iostream>
#include <chrono>
#include <memory>
#include <functional>
#include <atomic>
#include <string>
std::atomic_int64_t counter = 0;
// 定义回调接口
class UserInterface
{
public:
virtual void name() = 0;
virtual void full_name() = 0;
};
class User : public UserInterface
{
public:
void name() {}
void full_name() { counter++; }
};
void to_string(UserInterface* user)
{
user->name();
user->full_name();
}
using name_handler = std::function<void()>;
using full_name_handler = std::function<void()>;
class Test
{
name_handler name_;
full_name_handler full_name_;
public:
void set_name_handler(name_handler name)
{
name_ = name;
}
void set_full_name_handler(full_name_handler full_name)
{
full_name_ = full_name;
}
void to_string()
{
name_();
full_name_();
}
};
int main()
{
User user;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; i++)
{
to_string(&user);
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Counter: " << counter << std::endl;
std::cout << "Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << "us" << std::endl;
counter = 0;
auto name = []() {};
auto full_name = []() { counter++; };
Test test;
test.set_name_handler(name);
test.set_full_name_handler(full_name);
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000000; i++)
{
test.to_string();
}
end = std::chrono::high_resolution_clock::now();
std::cout << "Counter: " << counter << std::endl;
std::cout << "Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << "us" << std::endl;
return 0;
}
后记
查找资料的时候,翻到类似的代码片段 functionperformance.cpp
#include <iostream>
#include <chrono>
#include <memory>
#include <functional>
using namespace std;
using namespace std::chrono;
class Base
{
public:
Base(){}
virtual ~Base(){}
virtual int func(int i) = 0;
};
class Derived : public Base
{
public:
Derived(int base = 10) : base{base}
{
}
~Derived(){}
virtual int func(int i)
{
return i*base;
}
private:
int base;
};
struct Func
{
int base;
int operator()(int i)
{
return i*base;
}
Func(int base) : base {base}
{
}
};
const int base = 10;
int calculate(int i)
{
return base*i;
}
int main()
{
const int num = 10000;
Base *p = new Derived{10};
int total = 0;
auto start = high_resolution_clock::now();
for (int i = 0; i < num; ++i)
{
total += p->func(i);
}
auto end = high_resolution_clock::now();
std::cout<<"result: "<<total<<"\nvirtual call elapsed: \t"<<duration_cast<nanoseconds>(end-start).count()<<" nanoseconds.\n"<<std::endl;
total = 0;
start = high_resolution_clock::now();
for (int i = 0; i < num; ++i)
{
total += calculate(i);
}
end = high_resolution_clock::now();
std::cout<<"result: "<<total<<"\ndirect function call elapsed: \t"<<duration_cast<nanoseconds>(end-start).count()<<" nanoseconds.\n"<<std::endl;
Func functor{10};
total = 0;
start = high_resolution_clock::now();
for (int i = 0; i < num; ++i)
{
total += functor(i);
}
end = high_resolution_clock::now();
std::cout<<"result: "<<total<<"\nfunctor call elapsed: \t"<<duration_cast<nanoseconds>(end-start).count()<<" nanoseconds.\n"<<std::endl;
int base = 10;
function<int(int)> lambda = [base](int i)
{
return i*base;
};
total = 0;
start = high_resolution_clock::now();
for (int i = 0; i < num; ++i)
{
total += lambda(i);
}
end = high_resolution_clock::now();
std::cout<<"result: "<<total<<"\nlambda call elapsed: \t"<<duration_cast<nanoseconds>(end-start).count()<<" nanoseconds.\n"<<std::endl;
return 0;
}
/*
test on mac mini i7 2.7GHz
clang++ -std=c++11 chronotest.cpp -O0
output:
result: 499950000
virtual call elapsed: 43171 nanoseconds.
result: 499950000
direct function call elapsed: 31379 nanoseconds.
result: 499950000
functor call elapsed: 41497 nanoseconds.
result: 499950000
lambda call elapsed: 207416 nanoseconds.
===================================================
clang++ -std=c++11 chronotest.cpp -O1
output:
result: 499950000
virtual call elapsed: 26144 nanoseconds.
result: 499950000
direct function call elapsed: 22384 nanoseconds.
result: 499950000
functor call elapsed: 33477 nanoseconds.
result: 499950000
lambda call elapsed: 55799 nanoseconds.
===================================================
clang++ -std=c++11 chronotest.cpp -O2
result: 499950000
virtual call elapsed: 22284 nanoseconds.
result: 499950000
direct function call elapsed: 36 nanoseconds.
result: 499950000
functor call elapsed: 30 nanoseconds.
result: 499950000
lambda call elapsed: 28292 nanoseconds.
===================================================
clang++ -std=c++11 chronotest.cpp -O3
result: 499950000
virtual call elapsed: 18975 nanoseconds.
result: 499950000
direct function call elapsed: 29 nanoseconds.
result: 499950000
functor call elapsed: 30 nanoseconds.
result: 499950000
lambda call elapsed: 22542 nanoseconds.
===================================================
clang++ -std=c++11 chronotest.cpp -O4
result: 499950000
virtual call elapsed: 22141 nanoseconds.
result: 499950000
direct function call elapsed: 30 nanoseconds.
result: 499950000
functor call elapsed: 30 nanoseconds.
result: 499950000
lambda call elapsed: 22584 nanoseconds.
*/
这里多了两种模式,普通函数和仿函数,提供接口回调的方式和直接调用比较,性能损耗是数量级的差异,仿函数性能和函数接近,有时候仿函数的性能更优,编译原理这块算是知识盲区,猜测是由于访问的变量地址和函数挨着,有利于CPU
处理
附上 wandbox
运行结果
result: 499950000
virtual call elapsed: 6143 nanoseconds.
result: 499950000
direct function call elapsed: 30 nanoseconds.
result: 499950000
functor call elapsed: 31 nanoseconds.
result: 499950000
lambda call elapsed: 15134 nanoseconds.