循环优化
虽然增加并行运算的数量,是提高性能最有效的方法,但通过常规的循环优化,也能对性能达到实质性的改进。举例说明,请看下例可同时执行8次运算的FIR转换器实现(参见例3),之所以选择这个例子,是因为相对于可同时执行32次运算的例子,它更能保持内部代码简洁,以便更好地演示优化的技巧。
例3:
| #include "fir8.h" #define ST_DECR 1 #define ST_INCR 0 void fir(short *X, short *H, short *Y, short N, short T) { int n, t, t8; WR x, h, y; t8 = T/8; WRPUTINIT(ST_INCR, Y) ; for (n = 0; n < N; n++) { WRGET0INIT(ST_INCR, H) ; X++ ; WRGET1INIT(ST_DECR, X) ; WRGET0I( &h, 16 ); WRGET1I( &x, 16); FIR_MUL(x, h, &y); for (t = 1; t < t8; t++) { WRGET0I(&h, 16); WRGET1I(&x, 16); FIR_MAC(x, h, &y); } WRPUTI(y, 2) ; } WRPUTFLUSH0() ; WRPUTFLUSH1() ; } |
在这个实现中,扩展指令的使用,对函数fir()的性能,有极大的改善,可从27230个时钟周期降低到5065个时钟周期,而且无需低级汇编代码或对原始C语言算法结构进行较大的修改,然而,仍有浪费掉的时钟周期存在,所以此实现还是不够高效。为特定外部循环迭代而调用最后的FIR_MAC指令之后,处理器在取得结果之前,必须等待与扩展指令延迟相等的一定周期数,浪费掉的周期就由此而产生。
一种弥补的方法是,当在等待前一轮循环输出时,即刻开始下一轮循环的运算。因为完全发送扩展指令所需的处理器周期数,要远远大于等待结果所需的周期数,因此,完全可保证在当前例子发出第一条扩展指令时,处理器已可以使用前一个例子的输出了。
在循环迭代之间,也有进行优化的可能。某一特定外部循环迭代中的内部循环运算,是不依赖于前面或后面外部循环的结果的,如果此时可发出一条新的外部循环迭代,在某种程度上可补偿等待结果的延迟。可以设想这样一种情况,对0层外部循环迭代最后一条FIR_MAC发出后,CPU可即刻开始对1层外部循环迭代中的内部循环进行运算,而不必等待0层外部循环迭代完成后所发出的扩展指令。在此实现中,最后一条FIR_MAC在给出结果之前,将大概需要21个处理器周期。照这样,如果把外部循环中的内部循环运算进行流水线操作,极有可能把每次迭代所需处理器周期降低至18,当然这是除最后一个外部循环迭代之外,因为此时已没有更多的运算量可利用周期了。假定这种情况只会在每次fir()调用时发生一次,那么系统开销几乎可以忽略不计。
基于此技巧的实现(例4),已把每次调用所需的总周期数,从5065减少到3189。这样,对现有代码无需作更多的修改,就能带来超过8倍的性能提升。
例4:
| /* 包含特定的扩展指令头文件 */ #include "fir8.h" #define ST_DECR 1 /* 减量指示器 */ #define ST_INCR 0 /* 增量指示器 */ /* 为FIR ISEF 指令调用定义宏 */ #define FIR(H, X, h, x, t8, y) \ { \ int t8m1 = (t8)-1; \ WRGET0INIT(ST_INCR, (H)) ; \ (X)++ ; \ WRGET1INIT(ST_DECR, (X)) ; \ WRGET0I( &(h), 8 * sizeof(short) ); \ WRGET1I( &(x), 8 * sizeof(short) ); \ FIR_MUL( (x), (h), &(y) ); \ \ for (t = 1; t < (t8m1); t++) \ { \ WRGET0I( &(h), 16 ); \ WRGET1I( &(x), 16 ); \ FIR_MAC( (x), (h), &(y) ); \ } \ WRGET0I( &(h), 16 ); \ WRGET1I( &(x), 16 ); \ FIR_MAC( (x), (h), &(y) ); \ } /* * - 在ISEF中,FIR使用8倍乘法循环优化 */ void fir(short *X, short *H, short *Y, short N, short T) { int n, t, t8 ; WR x, h, y1, y2, y3, y4; t8 = T/8 ; WRPUTINIT(ST_INCR, Y) ; /* 起始输出流 */ FIR (H, X, h, x, t8, y1) ; /* x * h + y => y1 */ /* loop ((N/2)-1) times */ n = 0; do { FIR (H, X, h, x, t8, y2) ; /* x * h + y => y2 */ WRPUTI(y1, 2) ; /* 输出(y1)结果 */ FIR (H, X, h, x, t8, y1) ; /* x * h + y => y1 */ WRPUTI(y2, 2) ; /* 输出(y2)结果 */ } while ( ++n < ((N>>1)-1) ); FIR (H, X, h, x, t8, y2) ; /* x * h + y => y2 */ WRPUTI(y1, 2) ; /* 输出(y1)结果 */ WRPUTI(y2, 2) ; /* 输出(y2)结果 */ WRPUTFLUSH0() ; /* 清除输出流 */ WRPUTFLUSH1() ; /* 清除输出新 */ } |
另一个提升性能的技巧是手工解开内部循环。因为内部循环是通过使用一个变量来管理的,而FIR宏又是为通用目的编写的,所以,编译器不能预测到循环将执行的次数,也就是完全取决于内部循环本身。然而,这些条件又反过来影响扩展指令的调度,因为它们将清除(flush)处理器流水线,而处理器又错过了可发出一条扩展指令的周期,延迟就这样产生了。收藏 http://www.qqread.com/cpp/d652113002.html
相关图文阅读
频道图文推荐
健 康 咨 询
时 尚 咨 询
相关专题
- C/C++进阶技术文档 (821篇文章)
- 在Ubuntu 7.10中用终端编译运行C++程序 (0次浏览)
- C与C++在Linux下的集成问题 (0次浏览)
- 浅析C++中虚函数的调用及对象内部布局 (0次浏览)
- 在C++中实现四种进程或线程同步互斥的控制 (0次浏览)
- Ubuntu下面的C语言代码检查工具 Splint (0次浏览)



