本文主要是介绍【程序语言】元编程带来的代码展开技巧,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
我们讨论过对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 参数操作语句;
};
};
有闲心的朋友可以测试一下,这种方式和普通循环求和的效率差距,我自己做过测试,差距比想象中还大,但我自己一个人的机子不具有普遍说明意义,所有有空的朋友都可以考代码测试一下,欢迎贴结果在评论中!
这篇关于【程序语言】元编程带来的代码展开技巧的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!