有关C++模板inline的高性能在lambda与function的体现

前两天在群里跟人讨论到写库时对于lambda和function的取舍,跑了写测试查了些资料后基本得出结论:

如果没有自由变量的情况下,一般不要用function。

如果有自由变量的话,C++中的lambda就是一个匿名类的实例,而如果没有的话,就是一个单纯的函数指针。为什么说尽量不要用function呢?我们来看一下下面的代码:

void lambdatest(vector<int> data)
{
    sort(data.begin(), data.end(), [](int a, int b){ return a > b; });
    sort(data.begin(), data.end(), [](int a, int b){ return a < b; });
}

void functiontest(vector<int> data)
{
    sort(data.begin(), data.end(), function<bool(int, int)>([](int a, int b){return a > b; }));
    sort(data.begin(), data.end(), function<bool(int, int)>([](int a, int b){return a < b; }));
}

 

 我们随机大概1000000个数据点,然后做升降序排列,调用sort对function和lambda表达式进行比较。

最后我们profile的结果如下:

技术分享

最后那个qsorttest是调用了qsort函数。我们可以看到,function比lambda要慢几乎一倍以上,这个是release版,如果调成debug的话结果会更夸张一点,基本能到3~4倍,这是为什么呢?

这个要从C++模板开始说起了,首先我们都知道C++中的模板实例化是代码展开,就是指每次针对一个类型进行实例化都会将模板的代码拷贝一份,拷贝之后就可以对某一个类型进行特化,比如float可以用SSE指令等等,这个也包括这个实参有函数调用的话,可以进行内联。

优化的点就在内联上,也就是说,如果你想更快,就让模板对你的实参调用时准备一套专门的代码,然后内联。

那么我们怎么样能让内联起上作用呢?一般来讲,如果实参是一个函数指针,这个就没法内联了(其实也是可以的,不过这个我们在这里不讨论,跟优化实在关系不大),如果实参的函数是编译器绑定的,即stable name,我们就可以内联了。function这玩意儿本质上就是一个函数指针,运行的时候给他分配,所以不好内联;而lambda实际上是一个类型的实例,所以就可以。

我们现在来看编译器编译过程,编译器在编译同一个文件的时候,会为每个语义不同的lambda生成一个专门匿名类。在上面的测试代码中,sort函数会被分别实例化:

对于function,自然就是sort<int *, function<bool(int, int)>>。而lambda就是sort<int *, lambda_greater> 和 sort<int *, lambda_less>,lambda被实例化成了两份代码,但是在编译时编译器可以针对不同的匿名类做去特化,去内联。但是function就不行了,就只被实例化了一份代码,每次调用时都会调用,只是传入的函数指针不同,没法内联从而优化。

上面这段话其实可以通过profile来证实:

技术分享

技术分享

我们看到function调用了两次,而lambda因为内联只用了一次匿名类。

其实这个在C++98里一样,自己写functor或者std::less的时候效率高,但是一旦用的function时效率立刻下来了。这个就是C++模板里面inline的优势。

就是这样。其实很多东西最后讨论下来,没有太必要纠结这个,因为很多底层的优化并不是我们程序员考虑的,学习的很多东西都是很快会过时的,编译稍微加个参数立马就GG,所以大概明白一些基本的概念,然后在具体的实现时多profile,通过profile来推导编译的优化可能,从而来进行代码优化。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。