您的位置:首页 > 编程语言

【程序语言】元编程带来的代码展开技巧

2011-07-21 13:52 239 查看
我们讨论过对int arr[20]所有元素求和的最高执行效率代码,那就是:

int sum = arr[0]  +arr[1]  +arr[2]  +arr[3]
+arr[4]  +arr[5]  +arr[6]  +arr[7]
+arr[8]  +arr[9]  +arr[10] +arr[11]
+arr[12] +arr[13] +arr[14] +arr[15]
+arr[16] +arr[17] +arr[18] +arr[19];

但是这样写代码实在是太累了,为了效率,也不能这样写代码不是,要是我的数组是int arr[100]岂不是要写到无比长去!那么这次我们就通过元编程完成一次代码自动生成!

template <int Dim,typename T>
struct Sum{
static T sum(T *arr){
return (*arr) + Sum<Dim-1,T>::sum(arr+1);
};
};
template<typename T>
struct Sum<1,T>{
static T sum(T *arr){
return *arr;
};
};
/*调用代码*/
int sum = Sum<20,int>::sum(arr);

解释一下上面的代码:

step1:当读取到 Sum<20,int>::sum(arr) 时,编译器展开到 *arr + Sum<19,int>::sum(arr+1);

step2:当读取到 Sum<19,int>::sum(arr) 时,编译器展开到 *arr + *(arr+1) + Sum<18,int>::sum(arr+1+1);

...

编译器递归地展开上式...

编译器展开到 *arr +*(arr+1) +*(arr+2) +*(arr+3) +*(arr+4) +

*(arr+5) +*(arr+6) +*(arr+7) +*(arr+8) +*(arr+9) +

*(arr+10) +*(arr+11) +*(arr+12) +*(arr+13) +*(arr+14) +

*(arr+15) +*(arr+16) +*(arr+17) +*(arr+18) +Sum<1,int>::sum(arr+19);

最后按照struct Sum<1,T>的定义展开,即Sum<1,int>::sum(arr+19)会被展开成 *(arr+19);

同理,利用上面的代码,对int arr[100]进行求和的时候,只需调用Sum<int,100>::sum(arr)即可。

这样做的好处是什么呢?最直观的好处是代码效率的提高,循环是程序效率的关键,提高循环处得效率往往是最有效的。那么为什么这样做的效率会提高呢?

1.for(int i=0; i<100; ++i)这段代码,隐含了100处的判断,100处得自加。如果本身循环内只是做简单的加法,那么for循环的附加运算比本身的求和运算还多了。毕竟本身只做一百次求和而已!

2.for循环中的代码难以并行化,循环中的代码通常是串行执行的。但如果是简单的直接加,则编译器能优化出有效的并行指令!

如果将上面的例子继续通用化,我们就得到一种基本的技巧——"unroll the loop",即解循环,将循环用普通代码表示。如果程序中循环满足

1.循环位置是常数,例如20,100等

2.循环中语句是可按照常量分解的,如a[2]+a[4]+a[6]+a[8]+...

3.常量数列你能找到递推方式,如2中可能是2*i那么循环是可解开的。

解开的方式是

template <int Dim,typename T>
struct name{
static T function(T 参数){
return 参数操作语句 + Sum<Dim-1,T>::function(递推参数);
};
};

template<typename T>
struct name<1,T>{
static T function(T 参数){
return 参数操作语句;
};
};


有闲心的朋友可以测试一下,这种方式和普通循环求和的效率差距,我自己做过测试,差距比想象中还大,但我自己一个人的机子不具有普遍说明意义,所有有空的朋友都可以考代码测试一下,欢迎贴结果在评论中!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: