【程序语言】元编程带来的代码展开技巧
2011-07-21 13:52
239 查看
我们讨论过对int arr[20]所有元素求和的最高执行效率代码,那就是:
但是这样写代码实在是太累了,为了效率,也不能这样写代码不是,要是我的数组是int arr[100]岂不是要写到无比长去!那么这次我们就通过元编程完成一次代码自动生成!
解释一下上面的代码:
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那么循环是可解开的。
解开的方式是
有闲心的朋友可以测试一下,这种方式和普通循环求和的效率差距,我自己做过测试,差距比想象中还大,但我自己一个人的机子不具有普遍说明意义,所有有空的朋友都可以考代码测试一下,欢迎贴结果在评论中!
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 参数操作语句; }; };
有闲心的朋友可以测试一下,这种方式和普通循环求和的效率差距,我自己做过测试,差距比想象中还大,但我自己一个人的机子不具有普遍说明意义,所有有空的朋友都可以考代码测试一下,欢迎贴结果在评论中!
相关文章推荐
- PL/0语言编译程序整理实现:(8)、代码执行
- 常见各种语言编写的程序的入口点代码
- 关于小话C语言集合贴,C 语言常见问题集,c语言-优化C代码常用的几招,高效程序的秘密_hacker's delight读书笔记
- Java2实用教程(第二版)程序代码——第一章 Java语言入门
- 程序开发界面与代码分离技术-界面描述语言
- [置顶] PHP语言代码漏洞审计技巧笔记分享
- 第二篇T语言代码编写技巧
- 五行代码带来的无缝滚动程序
- 程序语言基础 8.14课堂代码
- ECMS 高级技巧之 list.var支持程序代码
- 计算机语言(c)的栈区,堆区,全局区,文字常量区,程序代码区解析
- 用 C 语言编写 Windows 服务程序的五个步骤(代码)
- 程序代码阅读技巧
- Windows Phone实用开发技巧(33):不重启程序切换当前语言
- [编写高质量代码:改善java程序的151个建议]建议85 小心switch带来的空值异常
- php语言中使用json的技巧及json的实现代码详解
- Android程序自动更改系统语言代码
- PL/0语言编译程序整理实现:(12)、测试代码
- 汇编语言32位控制台读取用户输入字符程序代码分析(17)
- Javascript 优化项目代码技巧之语言基础(二)