DSP应用程序中C代码和汇编代码的结合

本文作者:admin       点击: 2008-02-01 00:00
前言:
随着DSP处理器的功能日益强大,加上编译器的优化技术不断提高,以往只利用汇编语言编写DSP应用程序的普遍做法已逐渐被淘汰。今天,几乎所有的DSP应用程序都是由C代码和汇编代码共同构成。对于性能是核心的关键性功能,DSP工程师继续使用高度优化的汇编代码,而其它功能则采用C语言编写,以便于维护和移植。C代码和汇编代码的结合需要DSP工程师在工具箱中备有专用工具和方法。

众所周知,汇编代码编程的性能更好,而C代码编程的优势在于编写更为方便、快捷。为了解释清楚原因,让我们仔细对比一下汇编代码和C代码编程的优缺点。

汇编代码编程的优势

● 汇编代码能够利用处理器独有的指令和各种专用硬件资源。另一方面,C代码则是通用性的,必须支持不同的硬件平台,因此,对C代码来说,支持专用平台代码十分困难。
● 汇编代码的编程人员通常对应用非常熟悉,并可能做出编译器难以企及的设想。
● 汇编代码编程人员可以发挥人们的创造力;而编译器再先进也只是一个自动程序而已。
汇编代码编程的缺点:
● 汇编代码编程人员不得不处理耗时的机器级问题,比如寄存器分配和指令调度。而对C代码,这些问题都可交给编译器去做。
● 汇编代码编程需要拥有关于DSP架构及其指令集的专业知识,而C编码只需要掌握广为流行的C语言即可。
● 采用汇编代码,平台之间的应用移植极其困难和耗时。而C应用程序的移植要简单得多。

图1显示了如何利用专用硬件机制来高度优化汇编代码。左边的C代码实现方案利用模数运算创建了循环缓冲器。右边的是高度优化的汇编代码,利用CEVA-TeakLite-III DSP核的模数机制来创建相同的缓冲器。只要缓冲器指针(这里是r0)更新,该模数机制就自动执行模数算法。这种运算和指针更新发生在同一个周期内,故汇编代码比C代码有效得多,其可为模数运算产生单独的指令。

在DSP应用中选择适当的C和汇编代码混合

问题在于 C 代码和汇编代码之间的界限究竟在哪里,答案由分析器提供的性能分析给出。(要了解更多关于分析器的信息,参见“DSP 应用的编译程序优化”和“利用仿真器和分析器的DSP优化策略”两篇文章。) 不过,在使用分析器之前,DSP 工程师必需为应用定义明确的目标。这些目标一般包括指令周期数、代码量和数据量。一旦目标定义好,应首先完全采用 C 语言编写和构建应用程序。然后才使用分析器来分析性能。

在极少数情况下,主要是在控制应用中,C级编码就足够了。但大多数情况下,应用程序的初始C语言版本不能满足相关目标的要求。这通常意味着需要某种程度的汇编代码编程。在采用汇编代码编程之前,需要对C级代码进行许多处理以提高性能,不过这些处理工作不在本文范围内。(关于C级编码优化的一些技巧,请见“DSP 编程人员指南”)。在所有的C级处理工作都已完成,汇编代码编程开始之际,强烈建议保存原始C代码实现方案,这不仅便于调试,也能够在条件合适(如移植到功能更强大的平台) 的时候恢复C实现方案。

代码的汇编部分需尽可能地少,为此,应该对分析器报告的性能结果进行分析,对应用的关键功能进行确定。关键功能是指那些执行时间最长,故而应该采用汇编语言重新编写以满足性能目标的功能。一旦有两或三个最关键的功能被重新编写,就需要进行一次性能测试。如果应用仍不满足相关目标,就应该再定义更多的关键功能并进行汇编代码重新编写,这个过程不断反复,直到性能目标得到满足。
 
汇编编程人员关于编译器的考虑事项

在编写稍后将与C代码相结合的汇编代码时,汇编编程人员必需了解编译器的约定和假设 (conventions and assumptions)。最重要的编译器约定之一是函数调用约定(function calling convention),也被称为函数参数传递约定。这种约定描述了编译器在一个函数调用另一个时如何传递参数。为了成功地从一个C函数调用一个汇编函数 (反之亦然),该汇编函数必须返回参数并将之传递到函数调用约定定义的硬件资源上,通常是寄存器或堆栈存储器。

汇编编程人员还必须了解编译器的寄存器使用约定。这种约定把硬件寄存器划分为callee-saved (或 caller-used) (被调用者保存或调用者使用) 和callee-used (或 caller-saved) (被调用者使用或调用者保存) 寄存器。编译器假设callee-saved寄存器保存函数调用中的相关值。如果汇编编程人员需要使用这类寄存器,必须首先对之进行备份,然后在返回到C代码之前恢复它们的内容。反之,callee-used寄存器没有被假设为保存函数调用中的相关值。这意味着汇编编程人员可以无需备份就使用这些寄存器。不过,他们必需谨记,在汇编函数调用C函数时,这些寄存器可能会被被调用者写覆盖。

图2给出了CEVA-X1641 DSP内核FFT实现方案中采用汇编代码的例子。其黄色标注的adds指令遵守CEVA-X1641 编译器关于把指针参数传递到r0地址寄存器中的调用约定。蓝色的pushd 指令是备份函数中稍后会使用的callee-saved寄存器。

除了每个编译器都定义的调用约定和寄存器使用约定之外,有的编译器可能还有一些关于手工编写汇编代码的额外假设。这些假设常常是专用于某个编译器的,编译器供应商应该提供完善的资料和说明。这些DSP的编译器通常假设堆栈指针对准一个确定的宽度 (比如32位)。这就允许编译器优化堆栈的读写操作,并使用机器的全部存储带宽。它还需要汇编编程人员确定在调用C函数之前堆栈都已对准,否则可能会发生非对准访问。

编译器假设的另一个例子是手工编写汇编代码中特殊指令的位置。例如,CEVA-X1641编译器假设mov acX,rN指令 (把累加器转移到地址寄存器中) 可以绝不被用作汇编函数的第一条指令。这项假设可以实现更好的指令时序安排,同时填充call指令(调用一个函数) 的延迟时隙。像这样的独特假设通常可以利用专用编译选项来推翻。

用于C和汇编语言连接的常用C语言扩展

用于嵌入式平台的大多数编译器,尤其是那些针对DSP编程的编译器,都具有丰富的C代码和汇编代码连接功能,这些功能绝大部分都不属于标准C语言,故被称为C语言扩展。下面列出部分对DSP编程最有用的功能。

内嵌汇编 (Inline assembly)。这种功能可让编程人员在C代码中插入汇编指令,在必需直接访问设备资源时,它常常用于器件驱动器等底层C代码。这一功能在实现时,大多是编译器限制了关于插入指令的信息,因此对其特征做出最坏情况假设。这些假设可能会妨碍编译器的许多优化工作。例如,在支持某些指令 (但并非全部) 并行执行的架构中,编译器不能使插入指令和其它指令并行,因为这可能导致非法指令包出现。

硬件寄存器与C变量绑定。在绑定硬件寄存器和C变量时,C变量值是硬件寄存器值的映射,反之亦然。只要C变量被读取或写入,硬件寄存器就相应地被读取和写入。这一功能在底层代码中很常见,它常常结合汇编指令内联功能,允许内联汇编代码访问C变量。下面图3的例子即显示了内联汇编功能 (橙色标注) 和硬件寄存器绑定功能 (紫色标注)。

段属性 (Section attribute)。缺省条件下,编译器把全局C变量和函数分配给标准预定义存储段。这种段属性允许编程人员把它们分配给惟一的用户定义存储段。在稍后的链接阶段,这些段可能被映射到特定的存储地址。该功能让编程人员把C级别元素分配到准确的存储位置,这对DSP应用至关重要。
用户定义调用约定。如上所述,编译器有一个汇编编程人员必须遵循的预定义调用约定 (calling convention)。不过,在某些情况下,利用不同的调用约定也许能够更好地优化汇编功能。例如,设想编译器约定是在累积器中传递参数,这时,执行广泛地址计算的函数会更有效,如果它在地址寄存器中接收参数的话,在此情形下,对于特定函数,用户定义的调用约定可以选择另外的局部调用约定,这一功能依赖于和函数原型相关的专用语言,并会通知编译器调用约定被修改。

编译器内联函数。“编译器内联函数”是编译器内建功能的总称,利用专用宏或函数调用来触发。例如,CEVA-X 和 CEVA-TeakLite-III编译器就可为声音合成器中相当常见的ETSI/ITU 基本 DSP操作提供内联函数。对这些操作,编译器用其等效高度优化的汇编序列来取代每一个基本操作。
没有这类内建支持功能的编译器不得不调用用户定义函数来代替,这将导致两个主要的性能缺陷:首先,用户定义函数可能在循环中产生函数调用和返回 (如图4所示),这会造成极大的开销。其次,用户定义函数将如同所有其它的C代码一样被编译,这意味着用户定义函数可能性能较差。另一方面,具有内联支持功能的编译器已经内建最优化的实现方案。

图4显示了这一功能的重要性。图中,左边的C代码采用ETSI的 mult_r  (乘法和舍入) 基本操作。结果,CEVA-TeakLite-III编译器产生右边有效的实现方案。左边C代码和右边汇编代码中的mult_r 操作由紫色标注。
汇编内联函数。汇编内联函数 (Assembly intrinsics) 是把汇编代码内嵌到C代码中的先进方法,详述如下。

汇编内联函数 - 把汇编指令当作C语句一样来编写
前面提到的内嵌汇编功能有重大缺陷:
1. 当编译器不知道内嵌代码的内容因而采用最差情况假设时,它会妨碍编译器的多项优化工作。
2. 它可能让编程人员不得不去处理底层问题,比如寄存器分配和指令时序安排。

而这里的汇编内联功能在实现汇编代码的内嵌时却没有这些缺点。站在编程人员的角度,汇编内联函数看起来就像C语言的宏和函数。它们接收C变量,返回C输出,同时又表现为单个汇编指令。由于该功能涉及的所有东西都是C语言级别,故编程人员毋需担心寄存器分配、指令时序安排及其它底层问题。汇编内联函数不会妨碍编译器优化,而是参与优化,仿佛它们是编译器常规产生的汇编指令一样。这些特性使得让汇编内联函数的功能非常强大。

利用汇编内联函数,编程人员可以享用编译器不可能产生的独特汇编指令。这些汇编指令通常是为特定算法而量身定做。在适当的位置采用这些指令,可以大幅提高性能。例如,CEVA-X1641的 bitrev (位反向) 指令是为FFT等算法定制的。由于编译器不太可能把一个程序看作一个FFT并使用bitrev指令,故编程人员可以完全把bitrev汇编内联函数嵌入到C代码中。

结合编程人员对应用的透彻了解,汇编内联函数的功能变得更为强大。利用这种专业了解,编程人员可以在C应用程序的性能关键段里精确插入汇编内联函数序列。这样一来,编程人员能够确保编译器生成好像是手工编写一样的汇编代码。

采用C和汇编混合代码的应用程序调试

汇编代码的调试并非一件小事,它需要对延迟和存储对准限制等架构和机器级问题有深入的了解。只是简单地把C代码和汇编代码放在一起会使事情更棘手,因为编程人员现在还必须调试C代码和汇编代码之间的连接。

混合代码应用程序之调试的第一步就是分隔这一个问题。假设保持汇编代码的C级实现 - 并假设C实现方案工作顺利 - 对于其C实现方案,交换出汇编函数并重新测试应用程序便相当容易。为了迅速检测出问题,编程人员可以采用迭代过程:在每一步,把可疑函数的一半转换为相应的C实现方案,这样,在每一步中,编程人员都只需要测试前一步中函数的一半。

一旦有问题的汇编函数被确定,它就应该同时作为单独的汇编问题和C与汇编的连接问题被分析。调试单独的汇编问题对汇编编程人员来说十分简单明了,但C与汇编的连接问题就有点儿麻烦。不同于单独的汇编问题,在考虑汇编函数本身时,C与汇编的连接问题是不可见的。为了找出这些问题,编程人员必须检查编译器的约定,比如调用约定和寄存器使用约定。

编程人员还必须检核编译器假设,比如汇编指令的行踪。(重复前面提到的例子,CEVA-X1641编译器假设mov acX, rN 指令可以绝不会用作汇编函数的第一条指令。) 为了节省调试时间,编程人员应该在第一次执行汇编函数时,验证是否符合所有的编译器约定和假设。

H.264视频编码器和AMR-NB-真实
案例研究


在CEVA,这里描述的技术和方法可用于各种各样的应用,包括视频编解码器、音频编解码器、声音合成器和器件驱动器。在所有情况中,这些功能都可以大幅提高性能。

H.264视频编码器是很好的案例研究。它在处理能力 (通常以MHz衡量) 及其它资源方面要求非常严格,尤其是相比语音编解码器等其它类型的编解码器而言。CEVA利用它的高端CEVA-X16xx DSP 内核系列及其 MM2000多媒体平台,可提供这种编码器所需的处理能力。

可利用先进分析技术来确定这种编码器的关键函数,接下来对之进行优化。这些关键函数的优化过程是逐步完成的。首先,利用汇编内联函数这样的先进功能在C级对这些函数进行全面优化。然后,在汇编级对编译器提供的汇编代码作进一步优化。

图6所示为通过对这种编码器的关键函数进行全面优化所获得的性能提高。只有最后一个优化阶段涉及到完全的汇编代码编程。所有其它阶段都基于带有汇编内联函数的C代码。这些汇编内联函数主要用于SIMD (单指令多数据) 操作,如avg_acW_acX_acZ_4b。这个指令对8个输入字节执行字节平均,产生4字节结果。这种SIMD操作对执行大量字节级计算的视频编解码器非常有用。(鉴于这个原因,CEVA-X16xx架构为字节级的SIMD操作提供了广泛的支持。)
AMR-NB (自适应多速率 - 窄带) 是广泛用于无线通信应用中的语音编解码器。CEVA已经为所有DSP内核实现了这种声音合成器,但鉴于本文目的,我们只讨论CEVA-X1620实现方案。很多时候都是完全采用汇编来实现这种声音合成器,但利用这里描述的各种不同功能,C实现方案与CEVA-X1620编译器可以获得堪与汇编实现方案媲美的结果。提升CEVA-X1620编译器性能的最关键特性之一是支持ETSI内联函数。

图7显示了整个AMR-NB 应用的全部优化过程中的MCPS (每秒百万周期) 性能提高。只有最后的优化阶段涉及到了完全汇编代码编程。所有其它阶段都基于带有ETSI内联函数和汇编内联函数等内建功能的C代码。

总而言之,H.264 编码器和AMR-NB的例子清楚地显示了汇编实现方案的性能优势,但也表明纯汇编实现方案并非首选的优化方法。利用高质量软件开发工具链提供的各种C代码与汇编代码功能,DSP编程人员无需用汇编来实现整个应用程序就能够达到令人满意的性能结果。正如本文所述,编写C和汇编混合代码不是一件简单的工作。不过,这里讨论的各种功能有助于DSP工程师更轻松地完成这项任务。