您好,欢迎访问三七文档
当前位置:首页 > 行业资料 > 能源与动力工程 > 如何编写电力实时应用的运行在DSP上的高效C代码
如何编写电力实时应用的运行在DSP上的高效C代码张浩,郭经红国家电力公司电力自动化研究院通信研究所摘要:数字信号处理器(DSP)因其出色的处理能力,在包括电力应用在内的各个领域得到十分广泛的应用。为了满足电力应用的高实时性的要求,需要为DSP编写高效的代码。目前对DSP进行程序设计时,使用得最多的是C语言。本文介绍了DSP编程的特点,并从代码的空间效率和时间效率两个方面提出一些提高C代码效率的方法。关键字:DSP,高效C代码,空间效率,时间效率,电力实时应用1引言C语言是为人们所熟知的计算机语言。它运用灵活,功能强大,受到大多数程序员的青睐,也自然成为DSP程序设计的重要语言。目前,几乎所有的DSP都支持C语言和汇编语言的混合编程。但C语言较之汇编语言具有可移植性强、可读性强和编程周期短等优势,并且其代码效率也接近汇编语言。因此,大多数的DSP程序设计都是以C语言为主要设计语言,如何编写运行在DSP上的高效C代码成为DSP程序设计者最为关心的问题。2DSP编程的特点用C语言编写运行在DSP上的代码和编写运行在台式机上的代码是不太一样的。因此,很多用C语言编写代码(运行在台式机上的代码)的经验和原则,在对DSP进行C语言编程时,很可能不再适用。2.1DSP的存储容量有限DSP芯片都会有片内存储单元,根据厂商和型号的不同而拥有不同的容量。一般片内存储单元的容量都不大,即使扩展了外部存储单元,其总的容量也是比较有限的。另外,DSP对片内存储单元的访问速度要比对外部存储单元的访问速度快很多【3】。因此,如果能将整个代码空间(包括程序空间和数据空间)压缩到片内存储单元中,就可以极大地提高代码的执行速度。2.2DSP代码的执行时间有限DSP得到广泛应用的一个重要原因在于DSP在对数字信号进行数学处理时,体现出其无与伦比的速度优势。因此,DSP的应用场合一般都是对执行时间有严格限制的场合。例如电网通信、数据采集、输电保护和事故分析等都对系统的实时性提出了比较高的要求,通常系统的动作需要在很短的时间内完成。3编写高效的C代码由于DSP程序设计具有存储容量限制和执行时间限制的两个主要的特点,所以高效的C语言程序设计显得尤为重要。这里的高效指两个方面:空间效率高和时间效率高。空间效率高是指DSP代码尽量占用较少的代码空间;时间效率高是指DSP代码尽量在较短的时间内执行完毕。3.1提高代码的空间效率一段代码所占用的代码空间主要包括两个方面:程序空间和数据空间。程序空间是指代码中的C语言指令所占用的空间;数据空间是指代码中申明和定义的数组、变量和常量等所占用的空间。为了提高代码的空间效率,下面列出了几点建议:a)减少常量的申明和定义,多使用预定义语句“#define”。对于常量数组的申明和定义应该尽量减少,可能的话还可以通过对数据类型的调整来节省常量数组所占用的内存空间。对于一般的常量,都需要用预定义语句“#define”来进行预定义。这样,在编译的时候,常量以立即数的形式存在于程序空间中,但这并没有增加代码在程序空间上的开销。如果不使用预定义,而是申明和定义了一些常量,这些常量都要在数据空间中占用一部分存储单元。b)减少变量的申明和定义。在很多时候,我们都不能保证我们使用的变量数目是最少的。通常我们会使用冗余的变量来增加代码的可读性。这在其他情况下是无可厚非的,但是在对DSP进行程序设计的时候,我们需要保证我们使用了最少的变量。对于中转数据或临时存放数据的变量而言,很多是不必要的,可以设法将其删除。这样做有助于减小代码的数据空间,同时还减少了不必要的冗余操作,既提高的代码的空间效率,又提高了代码的时间效率。c)尽量使用全局变量。在一般的C语言程序设计中,我们不提倡过多地使用全局变量,因为全局变量的使用容易造成程序的结构模糊,函数之间的关联性增强,影响了程序的可读性和结构化【1】。但是在DSP的C语言程序设计中,由于程序的规模不大,并且以数据运算和硬件操作为主,因此全局变量的使用不会使程序变得难以理解。相反,因为全局变量在程序中容易观察,因此,给程序员对代码的剖析带来方便,有助于对代码的调试。绝大多数DSP代码都是面向过程的,并且以对全局变量的操作为基础,以实现其功能。而很多全局变量可以在代码执行的不同阶段被复用而不引起冲突,这样就大大节省了代码在数据空间上的开销。d)整个代码中反复使用的部分代码应该尽量写成函数的形式在代码中被调用。这样做可以减少代码的重复部分,节省了重复部分所占用的额外的程序空间。而由此带来的代码时间效率的下降是微乎其微的。e)尽量使用循环语句。循环语句的使用也在一定程度上减少的代码的重复,节省了程序空间。3.2提高代码的时间效率代码的时间效率也可以理解成代码执行时间的长短。为了能使代码在完成同样功能的前提下在更短的时间内执行完毕,需要对代码进行优化,下面列出了几条建议。3.2.1少用指针,多用下标这里指的是对数组的操作。在C语言的教材中一般都会提到数组操作的两种方式:指针方式和下标方式【1】。一般,教材会推荐使用指针方式,而指针方式也是C标准库函数中所使用的数组操作方式。也许指针方式在很多时候比下标方式来得更方便,然而下标方式比指针方式具有更高的效率。因此,在DSP程序设计中在能使用下标方式的地方尽量使用下标方式。例如下面一个初始化数组的函数,用指针方式写成:voidInitArray(int*array,intarrayLenth){intn;for(n=0;narrayLength;n++){*array++=0;}}用下标方式写成:voidInitArray(int*array,intarrayLenth){intn;for(n=0;narrayLength;n++){array[n]=0;}}用下标方式写成的函数,其执行的时间效率要比用指针方式写成的函数高很多。3.2.2少用判断语句和分支语句判断语句和分支语句的使用会打断流水线,因而严重降低程序的执行效率。从DSP的汇编指令集中可以看得很清楚,一条同时完成乘法和加法的复杂指令只需要DSP的一个指令周期,而一条简单的条件跳转指令则需要DSP的六条指令周期【4】【5】。究其原因,条件跳转语句将打断流水线。在C代码中,if…else…语句和switch…case…语句都属于条件跳转语句。因此,判断语句和分支语句的使用会降低代码的时间效率,要尽量少用。在实际操作时,可以通过各种方法来避免判断语句和分支语句的使用,用其他代码取而代之。例如下面的返回最大值的函数,如果使用判断语句,函数一般写成如下形式:intMaxOfTwo(inta,intb){if(ab){returna;}else{returnb;}}我们可以改写这个函数以避免判断语句的使用,改写后的函数如下:intMaxOfTwo(inta,intb){unsignedintc;c=ab;return(a-c*(a-b));}改写后的函数比改写前的函数具有更高的时间效率。3.2.3register关键字的使用在多数DSP程序设计中,都允许使用register关键字【2】。它的作用就在于将register关键字所修饰的变量放在DSP的寄存器中,而不是放在片内存储器中。由于DSP对寄存器的访问速度比对片内存储器的访问速度还要快,因此提高了代码的时间效率。特别是作为循环的计数变量,更需要放入寄存器中。例如前面提到的初始化数组的函数,如果写成:voidInitArray(int*array,intarrayLenth){registerintn;for(n=0;narrayLength;n++){array[n]=0;}}则变量n将被放入寄存器。虽然多数DSP编译器的自动优化功能有时候会根据自己的判断自动将一些没有进行register修饰的变量放入寄存器,但是编译器的选择往往不是最明智的,需要人为添加register修饰词。如果register变量太多,以至于寄存器的数目不够,编译器会忽视一部分register修饰词,因此不会造成程序的错误。但这不代表所有的变量都可以放入寄存器中。为了能真正发挥register关键字的作用,在选择被修饰的变量时需要作充分的考虑。3.2.4循环嵌套问题循环语句是程序设计中常用的语句,有时候甚至需要嵌套使用。在嵌套时有一个原则,外循环的循环次数尽量少,内循环的循环次数尽量多。因为程序在进入循环的时候需要设置一些寄存器,外循环的次数太多会使得程序多次进入内循环而带来较大的时间开销。例如:for(m=0;m10;m++){for(n=0;n1000;n++){……}}这段代码的执行效率要比下面代码的执行效率高。for(m=0;m1000;m++){for(n=0;n10;n++){……}}3.2.5减少除法的使用除法运算无论在浮点DSP还是定点DSP中都是比较复杂的,需要消耗比较多的指令周期【6】。因此需要尽量减少除法的使用,特别是大规模除法的使用。在某些细节上也应该注意用其他更高效的指令来取代除法。例如,在进行除以2和除以4等除以2的幂次的操作时,用移位的方式来代替。这样做的效果是一样的,但是执行的时间效率却要高很多。在很多对数据进行放大和缩小的操作中,如果缩放的倍数不需要十分精确,也完全可以用移位来代替。3.2.6使用查表法查表法是一种常用的方法,在很多时候牺牲一些数据空间建立一个表,换来的是时间效率的极大提高。查表法在信道编码中的使用尤为常见,例如Viterbi编码。如果不使用查表的方法,Viterbi编码需要通过对移位寄存器中的各个位进行处理,以得到编码的结果。这个过程虽然不是很复杂,但是和查表的方法相比,其时间效率还是非常低的。如果我们建立一个表,表的下标是移位寄存器的状态,用二进制数表示;表的内容是移位寄存器在该状态下时Viterbi编码器所对应的输出。这样就建立了编码移位寄存器状态和Viterbi编码器输出的单射关系。通过移位寄存器的状态来查表,直接得到Viterbi编码器的输出。查表的方法省去了编码的过程,大大提高了代码的时间效率。在上面提到的减少除法的使用时,我们介绍了一些简单的取代除法运算的其他操作。实际上,查表的方法也是一种可以取代除法运算的方法。除以一个数,等于乘以一个数的倒数。我们可以通过事先建立倒数表的方法,来变除法为乘法。这样的方法对被除数的范围有一定要求,范围不能太大。否则,要么表的规模太大,要么计算精度会下降得比较厉害。但这不失为是一种取代除法运算的很好的方法,合理得建立倒数表可以很好得解决这种存储空间和计算精度的矛盾。3.2.7算法的优化最后要提到的,实际上也最重要的就是算法的优化。精练的算法比粗糙的算法时间效率高很多,甚至不在一个数量级上。因此,最见效也是最关键的在于选择最优的算法。算法选择不当,再怎么做细枝末节的优化和调整,其效果也不甚明显。因此。如果要提高代码的时间效率,对于算法的优化应该是排在第一位的。在算法的优化完成以后,再去考虑上面提高的或者没有提到的其他各种优化的方法,这样才能从根本上提高代码的时间效率。4结论DSP是一个硬件资源有限的环境,同时又进行着大量的实时数据处理,因此需要在对DSP进行C语言程序设计的时候,特别注意代码的效率。文章中分别给出了几点提高代码的空间效率和时间效率的方法。而事实上,代码的空间效率和时间效率是一对矛盾。通常空间效率的提高要牺牲一些时间效率,而时间效率的提高也需要以空间效率为代价。这一点在查表方法的使用上体现的十分明显,查表本身就是用空间换来时间的典型方法。写出高效C代码的关键就在于通过各种方法来处理好这对矛盾,甚至使这两者同时得到某种程度的改善。参考文献【1】[美]赫伯特·希尔特,《C语言大全(第四版)》,电子工业出版社,2001年9月【2】“TMS320C55xOptimizingC/C++CompilerUser’sGuide”,TexasInstrumentsApplicationReportSPRU281D,July2002.【3】“TMS320C55xDSPCPUReferen
本文标题:如何编写电力实时应用的运行在DSP上的高效C代码
链接地址:https://www.777doc.com/doc-58452 .html