最近作了一个代码优化相关的总结,这里记录一下,这里一般指的是DSP,ARM这类芯片,一般不涉及多核。
1. 三个不同的优化:
(1) 等价优化:如多余的计算,内存的访问次序等 ,不必要的计算,内存对齐等等,这些基本上对结果不会有改变,但是对开销影响可能会比较大;
(2) 非等价优化:如数学函数的等价优化、定点化等;
(3) 利用器件特性的优化:如SIMD的优化,嵌入式汇编的使用。
2. 优化的方法:
优化的最终目标是减少程序执行开销,那么任何能达到这个目的都是可取的。这里分别从开销涉及到的维度进行介绍:
(1) 内存访问:
对于较大规模数据的运算,涉及到的内存访问会占用较大比重,尤其是数据在外存的情况下。
1)减少内存访问;
2)充分利用cache,减少内存访问miss;
3)减少非连续内存访问;
4) 变量内存对齐;
(2) 单运算单元:
一般运算单元都可以除理+、-、x、位运算、逻辑运算、调转等功能,一般的DSP和ARM是不支持直接的/运算的,一般对于/、mod都是调用算学库来算的,开销较大。
1)尽量少使用/、%这类指令,可以使用乘法、自定义除法、==、&=替代;
2) 使用近似替代运算: 如sqrt(a^2+b^2) 用 max(abs(a),abs(b))或用其他平滑方法来作;
3)使用开销小的等价运算替代:如log(sqrt(a^2+b^2)) = log(a^2+b^2) /2 = log(a^2+b^2)*0.5;
4) 减少不必要的计算;
(3) SIMD类:这个就没啥 说的了,充分利用总线带宽,单指令实现多个数据的相同的操作,减少代码执行时间,如arm的NEON指令优化,TI DSP的SIMD指令,X86的SSE等。
(4) 汇编:
从代码维护角度而言,不建议编写纯汇编的函数或文件 ,一般只会在basic_op上使用部分嵌入式汇编。
3. 一些例子:
(1) 能在循环外做的就在外面做:
code2与code1完全等价,但是把每次的乘累加变成了累加再乘,计算量更少。
能在循环外做的就在外面做,毕竟外面做的代价最小。
(2) 尽可能顺序访问:
以上两个代码的功能是相同的,都是作w * input的矩阵计算,但是code1中用的是非连续访问,cache miss很大, 而code2中对w_T是连续访问,开销会小很多。
(3) 减少内存访问:
一个比较常见的例子就是语音buffer,可能某些场景下,需要一个很大的buffer,那么就涉及到这个buffer的更新问题。
最简单的方法是:每次更新时把最新的压到最后,必然会涉及到历史数据的推移,当buffer较大时,就涉及到一个较大内存块的读取和写入;
比较经济的作法是循环buffer方式,需要有一个指针指向最新写入点的位置,读取或写入时都要根据这个位置来进行,而且会涉及到从buffer结尾切到buffer起始判断,但不可否认的是,这种方法将对内存声的考贝减到最少了。