DSP应用中编译选项的智能选择
作者:CEVA公司编译器项目经理Eran Balaish
摘要
随着DSP处理器的能力越来越强大,可采用C编译器的代码部分在不断增加。不过,没有编程人员的协助,编译器是无法生成最优化的代码。为了最大地提高性能,编程人员必须利用各种编译选项功能来调节编译器。
不幸的是,在DSP应用里,没有充分利用编译器调节能力的现象相当普遍。整个应用过程中,往往只利用一组相同的编译选项来进行编译。这种方法忽略了每一项功能的特殊需求。
编译选项的智能选择可以大幅提高性能,例如可以大大减少代码量。在评估产品成本时,代码量通常是主要考虑因素,因为这对所需存储量有直接的影响。本文将介绍如何减少代码的使用量以及其它重要资源的消耗量。
在指令周期数和代码量之间进行权衡,满足功能级的特殊要求
为了更好地了解如何利用编译选项的智能选择来节省代码量,必须熟悉指令周期数与代码量之间的权衡取舍,这方面的实例是循环展开 (loop-unrolling) 和软件流水(SWP) 的常用编译器优化技术。
这种技术通过循环体的复制来进行循环展开,利用从循环语句内部到循环语句外部的某些指令的拷贝来实现SWP。当使用带有深度管线的多事件超长指令字 (VLIW)处理器时,该技术显得非常有用。这时,SWP可中断循环语句内部的许多依赖关系,而循环展开能够大幅提高指令间并行性 (ILP)。
这里给出了简单的乘法与累加 (mac) 循环语句,以阐释循环展开和SWP。
for (i = 0; i < 960; i++)
{
sum += src1[i] * src2[i];
}
图1. 基本的C语言级mac循环语句
下面摘选了一段效率相对低的简单汇编代码,是在为代码量最小化而调节CEVA-X1641™ DSP内核编译器时产生的。它使用了SWP,但没有循环展开。这个循环语句的执行需要使用大约959*2 = 1918个指令周期,所需代码量小于20个字节。
LS0.ld {w} (r3)+#2, a3l
LS0.ld {w} (r4)+#2, a2l
SQ.bkrep {ds1} #958
nop
{
nop
A.S.mac a2l, a3l, a0
|| LS0.ld {w} (r3)+#2, a3l
|| LS1.ld {w} (r4)+#2, a2l
}
图2. CEVA-X1641编译器产生的mac循环语句中的简单但效率较低的
汇编实现方案
相反地,当为指令周期数最小化而调节时,CEVA-X1641编译器产生的结果截然不同。在下面的例子中,可以看到CEVA-X1641编译器处于满功率状态,充分利用了CEVA-X1641 Quad-Mac架构提供的全部硬件。在每一个周期中,所有四个mac 单元和整个128位存储带宽都同时使用。编译器展开循环语句8次,并充分利用SWP。该循环语句的执行需要用大约119*2 = 238个指令周期,所需代码量超过100多字节。这比前面的循环实现方案快了8倍,但代价是代码量增加到5倍。
LS0.ld {2dw} (r3)+#8, a5, a7
SQ.bkrep {ds1} #118
|| LS1.ld {2dw} (r4)+#8, a4, a6
|| LS0.ld {2dw} (r3)+#8, a17, a3
LS1.ld {2dw} (r4)+#8, a16, a2
{
A.M1.mpy a6l, a7l
|| A.M0.mac3 a6h, a7h, a18
|| A.L.mpy a4l, a5l
|| A.S.mac3 a4h, a5h, a0
|| LS0.ld {2dw} (r3)+#8, a5, a7
|| LS1.ld {2dw} (r4)+#8, a4, a6
A.M1.mpy a2l, a3l
|| A.M0.mac3 a2h, a3h, a18
|| A.L.mpy a16l, a17l
|| A.S.mac3 a16h, a17h, a0
|| LS0.ld {2dw} (r3)+#8, a17, a3
|| LS1.ld {2dw} (r4)+#8, a16, a2
}
A.M1.mpy a6l, a7l
|| A.M0.mac3 a6h, a7h, a18
|| A.L.mpy a4l, a5l
|| A.S.mac3 a4h, a5h, a0
A.L.mpy a16l, a17l
|| A.S.mac3 a16h, a17h, a0
|| A.M1.mpy a2l, a3l
|| A.M0.mac3 a2h, a3h, a18
图3. CEVA-X1641编译器产生的mac循环语句中的效率最优化但代码量较大的汇
编实现方案
ILP的提高将显著提升性能。不过,上面提到的大量代码复制可能需要额外的存储空间,这也许会超过正常的嵌入式应用限制。那么如何能够在提高性能的同时又不必增加存储容量呢?很快我们就会到解决办法。但有一件事是确定的,在许多嵌入式应用中,指令周期数的增加通常意味着代码量的增加,反之亦然。
分析器 -- 大型应用中完成优化工作的最重要工具
在大型应用的优化中,好的分析器必不可少。它可以提供有关每个功能非常有用的信息,比如指令周期数、代码量、调用次数乃至存储冲突、缓存未命中 (cache misses) 等缓存相关信息。
尽管缓存相关信息对存储管理非常有用,但在选择编译选项时却用处不大。对我们的需求来说,最有用的参数是指令周期数和代码量。代码量是静态计算的,而指令周期数则是运行时获得的。
下面是CEVA的SmartNcode™ Profiler分析器提供的分析结果实例。被分析的应用是AMR-NB (自适应多速率 – 窄带) 语音编解码器。
图4. 分析结果实例
c语言编译器ide代码编辑‘Excl’’代表每功能专用的指令周期数,意指只考虑该功能自身消耗的指令周期数,而没有包括它调用的其它功能消耗的指令周期。综合信息也可以获得,但对我们的目标来说用处不大。Av、Min 和 Max分别表示平均、最小和最大。
如何根据分析信息选择最佳编译选项
首先,需要选中一组编译选项以供选择。在流程末端每一个源文件都利用这组选项中的一个进行编译。例如,在CEVA-X1641编译器中,这一组选项可以如下:{-O3, -O4, -O3 -Os1, -O3 -Os2, -O3 -Os3, -O3 -Os4},这里 -OX为指令周期优化选项,而 -OsY是代码量优化选项。X或Y因子越大,优化程度就越高。
一旦最优化的选项组被确定,就对每一个选项执行下列步骤:
1.构建应用。
2.设置分析器以收集运行时间信息并执行应用。
3.存储分析信息以供稍后分析之用。
若有了关于每一个编译选项的分析信息,就可以利用电子表格把所有的结果存储在一个表中。
图5. 在汇总表中分析所有编译选项的信息
现在可以利用设置电子表格特性的规则,根据相关分析信息自动为每项功能选择编译选项。例如,可以决定在规定的代码量定界符(比如1KB) 内为每一项功能选择最快的选项。这样,最大的总代码量就是设
置的定界符乘以应用中的功能数目。另一个可行方案是把指令周期数最少的选项用于那些需要消耗一定数量的总指令周期数(如大于1%) 的功能,而把代码量最小的选项用于其余的功能。CEVA的SmartNcode 工具链使用的是嵌入了先进调节能力的更加复杂的规则。这些调节能力可以根据应用的特定要求让这种考虑规则偏向代码量胜于指令周期数,反之亦可。
如何利用标准工具链配置带有所选编译选项的项目
每一项功能有最适合的编译选项是不够的。标准工具链只支持文件级上而非功能级上的编译选项指定。这就直接导致下列问题:如果同一个文件中有几项功能,我们应该做什么?每一个功能是否需要不同的编译选项?对此有几个可行的解决方案:
1.使用CEVA的每功能编译选项设置SmartNcode功能。这种功能实际上可让
用户忽略上述问题,因为它不再存在了。该方案可以与上一级的选择保持一致。
2.以某种形式重新安排应用,以便于利用相同的编译选项对一个给定文件中的
所有功能进行编译。换言之,利用适合于其所包含的全部功能的编译选项来编译每一个文件。类似于上一种方法,这也允许编程人员与上一级的选择保持一致。
3.放弃或选择一个编译选项都是基于这一种设想:在同一个文件中有多项要求
不同的功能。例如,如果单个文件中有两项功能,一个需要 -O3 -Os1 选项,另一个需要 -O3 -Os3 选项,则放弃 -O3 -Os2 选项就是很好的选择。在较为重要的情况中,应该检查涉及到的全部功能的所有分析结果,并基于此出一个良好的折衷办法。这种方案不同于前两种,它不允许用户与上一级的选择保持一致。
对功能级设置编译选项的特殊支持
如果编译器支持功能级的编译选项,就不需要前节所述的规避方案了。CEVA的SmartNcode工具链就提供这种支持。这种集成开发环境 (IDE) 为设置每功能编译选项提供用户友好图形界面。IDE以XML文件的形式把这种信息传送给编译器。高级用户可以无需使用IDE直接编辑XML文件。
下图是设置每功能编译选项的用户界面。在演示项目的demo.c文件中有两项功能― foo1 和 foo2:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论