考虑使用函数对象代替函数作算法的参数(Effective STL条款46)

  

一般我们都会这样认为,用高级语言编程时抽象层次越高,产生的代码效率就越低。这在很多情况下都是事实,Alexander Stepano (STL的发明者) 有一次作了一组小测试,试图测量出相对于C而言C++的“抽象惩罚”:

 

    

假设你需要以降序排序一个doublevector。最直接的STL方式是通过sort算法和greater<double>类型的函数对象:

 

vector<double> v;

//...初始化V

sort(v.begin(), v.end(), greater<double>());

如果你注意了抽象惩罚,你可能决定避开函数对象而使用真的函数,一个不光是真的,而且是内联的函数:

inline

bool doubleGreater(double d1, double d2)

{

return d1 > d2;

}

//...

sort(v.begin(), v.end(), doubleGreater);

下面是我测试用的代码,在Release模式下,设置编译器优化为最快速度。

#include <iostream>

#include <vector>

#include <functional>

#include <algorithm>

#include <time.h>

using namespace std;

inline bool doubleGlater(double d1,double d2){//大于测试

    return d1>d2;

}

inline bool doubleLesser(double d1,double d2){//小于测试

    return d1<d2;

}

int main()

{

    vector<double> vecd1;

    vector<double> vecd2;

    srand(time(NULL));

    for (int i=0;i<4500000;++i)

    {

        int i=rand();

        vecd1.push_back(i);

        vecd2.push_back(i);

    }//用随机数初始化两个vector

 

    clock_t start,finish;

    double totaltime;

    start=clock();

    sort(vecd1.begin(),vecd1.end(),doubleGlater);//用真函数排序

    sort(vecd1.begin(),vecd1.end(),doubleLesser);//先大于,再小于排序

    finish=clock();

    totaltime=(double)(finish-start)/CLOCKS_PER_SEC;

    cout<<"doubleGlater: "<<totaltime<<endl;

 

    double totaltime1;

    start=clock();

    sort(vecd2.begin(),vecd2.end(),greater<double>());//用仿函数排序

    sort(vecd2.begin(),vecd2.end(),less<double>());//先大于,再小于排序

    finish=clock();

    totaltime1=(double)(finish-start)/CLOCKS_PER_SEC;

    cout<<"Glater(): "<<totaltime1<<endl;

    return 0;

}

结果令人大跌眼镜,真函数版本用了7.374 s,而仿函数版本却只用了1.782 s

 

难以相信吧,仿函数竟然比真函数快了4倍,这个行为的解释很简单:内联。如果一个函数对象的operator()函数被声明为内联(不管显式地通过inline或者隐式地通过定义在它的类定义中),编译器就可以获得那个函数的函数体,而且大部分编译器喜欢在调用算法的模板实例化时内联那个函数。在上面的例子中,greater<double>::operator()是一个内联函数,所以编译器在实例化sort时内联展开它。结果,sort没有包含一次函数调用,而且编译器可以对这个没有调用操作的代码进行其他情况下不经常进行的优化。

使用doubleGreater调用sort的情况则不同。要知道有什么不同,我们必须知道不可能把一个函数作为参数传给另一个函数。当我们试图把一个函数作为参数时,编译器默默地把函数转化为一个指向那个函数的指针,而那个指针是我们真正传递的。因此,这个调用 

sort(v.begin(), v.end(), doubleGreater);

不是真的把doubleGreater传给sort,它传了一个doubleGreater的指针。当sort模板实例化时,这是产生函数的声明:

 

void sort(vector<double>::iterator first, // 区间起点

vector<double>::iterator last,            // 区间终点

bool (*comp)(double, double));            // 比较函数

因为comp是一个指向函数的指针,每次在sort中用到时,编译器产生一个间接函数调用——通过指针调用。大部分编译器不会试图去内联通过函数指针调用的函数,甚至,正如本例中,那个函数已经声明为inline而且这个优化看起来很直接。

  
把函数指针作为参数会抑制内联的事实解释了一个长期使用 C 的程序员经常发现却难以相信的现象:在速度上, C++ sort 实际上总是使 C qsort 感到窘迫。当然, C++ 有函数、实例化的类模板和看起来很有趣的 operator () 函数需要调用,而 C 只是进行简单的函数调用,但所有的 C++“ 开销”都在编译期被吸收。在运行期, sort 内联调用它的比较函数(假设比较函数已经被声明为 inline 而且它的函数体在编译期可以得到)而 qsort 通过一个指针调用它的比较函数。结果是 sort 运行得更快。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值