科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件用C语言加速程序进而加速硬件速度

用C语言加速程序进而加速硬件速度

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文将探讨通过C语言,怎样加速程序处理速度,以可配置软件架构的方式,达到硬件加速的目的。

作者:谢启东编译 来源:天极开发 2007年10月27日

关键字: Linux

  • 评论
  • 分享微博
  • 分享邮件
循环优化

  虽然增加并行运算的数量,是提高性能最有效的方法,但通过常规的循环优化,也能对性能达到实质性的改进。举例说明,请看下例可同时执行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)处理器流水线,而处理器又错过了可发出一条扩展指令的周期,延迟就这样产生了。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章