一般我们都会这样认为,用高级语言编程时抽象层次越高,产生的代码效率就越低。这在很多情况下都是事实,Alexander Stepano (STL的发明者) 有一次作了一组小测试,试图测量出相对于C而言,C++的“抽象惩罚”:
假设你需要以降序排序一个double的vector。最直接的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而且这个优化看起来很直接。