DSP

大话处理器之编写高效的代码总结

2019-07-13 19:19发布

2011.11.15,阅读《大话处理器》第6章《编写高效的代码—时间就是生命》,主要内容笔记如下。 Profile在美剧《犯罪心理》中的意思是剖析犯罪分子的心理。在软件开发中,就叫软件性能剖析。性能剖析工具分析每个函数(有的工具能分析到每个循环)的执行时间。 常用的性能剖析软件有:IBM的Rational Quantify、Intel的VTune、AMD的CodeAnalyst,DSP的软件集成开发环境中也自带有这种工具。 (一)  减少指令数; 1.      使用更快的算法—算法,程序设计的灵魂;(高斯的故事) 2.      选用合适的指令—合适的人做合适的事;(主要是处理器的专用汇编指令) 3.      降低数据精度—比特也要省着用;(单精度浮点数绝对值函数fabsf()比双精度fabs()快) 4.      减少函数调用—不要老打断我; a)      将小函数直接写成语句; b)      将小函数写成宏; c)       将函数声明为内联函数; 5.      空间交换时间;(Fibonacci数列某项值的计算、Google百度搜索引擎算法) 6.      减少过保护—打不破的部门墙;(函数入参检查、保护可适当去掉) (二)  减少处理器不擅长的操作—不要逼我做我不喜欢的事情; 1.      少用乘法;(可转化为移位运算) 2.      少用除法、求余; 3.      在精度允许的条件下,将浮点数定点化; 《反恐精英》中扔了烟雾弹之后的半透明烟雾效果,使用图像的alpha混合:将两张图像进行半透明的混合来实现,混合后新图像的每个像素的颜 {MOD}值为: Pixel_C =(int)(Pixel_A * alpha + Pixel_B * (1 – alpha)); alpha为透明度,是介于0和1之间的小数,随着之间的推移,烟雾图像的alpha值越来越小。当alpha为0时,烟雾效果完全消失。          由于浮点运算耗时,可以将alpha定点化为0~32之间的一个整数,0表示完全显示B图像,32表示完全显示A图像,中间分为32个等级,代码变为 Pixel_C =(Pixel_A * alpha + Pixel_B * (32 – alpha) + 16) >> 5); 由于全部都是定点语句,执行时间大幅减少。 4.      尽量减少分支;(if和switch跳转语句会打乱流水线执行,影响效率) 5.      将最有可能进入的分支放在if中,而不是else中; (三)  优化内存访问 1.      少使用数组,少使用指针;(用局部变量代替) 2.      少使用全局变量;(用局部变量代替) 3.      一次多访问一些数据; 4.      数据对齐访问;对2字节变量,它的起始地址应该为2的整数倍;对于4字节变量,它的起始地址应该为4的整数倍;对于8字节变量亦是如此; 5.      大数据结构时的Cacheline对齐;(Intel处理器的Cache line大多为64 byte,在对大数据结构分配内存时,起始地址最好为64 byte的整数倍,这样Cachemiss的次数最少。) 6.      程序、数据访问符合Cache的时间、空间局部性;(两重for循环处理二维数组例子;程序组织、存储符合Cache局部性原则) 7.      多线程编程时,避免falsesharing; 8.      自己管理内存的动态分配; 9.      隐藏数据搬移时间;(根据处理器支持,用DMA将SRAM中数据搬移到处理器、Cache预取机制) (四)  充分利用编译期进行优化—编译器:我才是优化第一高手; 1.      编译器提供了分级优化选项; 2.      简化表达式;(消除重复计算) 3.      提取公共代码;(把两个分支中的公共代码提到外面) 4.      循环展开、软件流水;(比如可以使用编译期的预编译指令) 5.      自动向量化;(使用关键字restrict) 6.      高效的数据组织;(编译器对变量、函数分配存储空间,减少Cache miss) 7.      指令并行化; (五)  利用多核加速程序—人多力量大; 1.      并行计算;(分工:任务划分、数据划分、数据流划分;Amdahl’s law,阿姆达尔定律:串行与并行分开;多线程编程) 2.      OpenMP;(并行编程框架,适合共享内存系统、多核处理器)     1.      减少重复计算 for (i =0; i < strlen(pswd); i++) {     if (isNum(pswd[i]))     {         j++;     } } ...   2.  减少分支 for (i =0; i < 100; i++) {     if (i % 2 == 0)     {         a[i] = x;     }     else     {         a[i] = y;     } } 修改后: for (i =0; i < 100; i+=2) {     a[i] = x;     a[i+1] = y; }   3.  程序、数据访问符合Cache的时间、空间局部性: for (j< 0; j < 500; j++) {     for (i < 0; i < 500; i++)     {         sum += a[i][j];     } } 优化后: for (i< 0; i < 500; i++) {     for (j < 0; j < 500; j++)     {         sum += a[i][j];     } }   4.  自动向量化: void add(float *restrict a, float *restrict b, float *restrict c) {     int i;     for (i = 0; i < 4; i++)     {         c[i] = a[i] + b[i];     } } retrict关键字告诉编译期a、b、c三个指针指向的空间是没有交叠的,这样编译器可对它们做合适的优化。编译器能使用SIMD指令来优化。 /* VC 6.0帮助文档 */ i = 100; while (i< 0) {     i += x + y;     *p = i; } 如果使用Assume No aliasing编译选项,编译期能将x+y放到循环外面;否则不会,因为指针p可能指向x或y;   5.  指令并行化: x = a +b; y = x +c; z = y +d; 优化为: x = a +b; y = c +d; z = x +y;