以上章节讲述了在ADI Blackfin系列上的C语言优化方法,如何利用DSP的特性进行循环和条件控制语句的具体优化策略,该部分则主要讲述如何优化memory的性能,以及如何实现速度speed vs. 代码大小size的折衷。
Programming and Optimizing C Code on Blackfin ADI Blackfin处理器的编程与优化 Part 5. Memory优化
内存性能
编译器往往把内存视为统一的无限大小的资源,可以在存取是不存在任何额外的开销。这是个理想的flat memory模型,但是在现实中,存储器的存取开销将直接影响你的应用程序的性能。因此我们需要学习内存对我们程序性能的影响。大多数DSPs都能访问内部(on-chip)和外部(off-chip)的内存空间。但是片内的快速存储器(通常静态存储器)和片外的慢速内存(通常SDRAM)有非常巨大的性能差距。编译器很少能自动智能的防止数据的代码,因此需要由程序员把程序内最most-critical程序代码和数据放在片内的存储空间,而其他的放在片外。这通常是通过一个连接器控制文件来完成的。你也可以告诉链接器如何将特定的数组和代码放置在存储空间。例如,下面的声明告诉链接器将数组放在L1 data数据空间。
片内存储器的访问速度取决于数据访问模式,在理想的情况下,大多数DSPs可以在每个周期内访问两个数据。然而DSP的片内存储器通常是分成几个Bank, 同一个Bank如果接收到两个同时的数据访问请求时会产生stall。不幸的是,编译器不知道是否两个数据是否会分布在同一个Bank因为数据的放置位置是由链接器完成的。一个比较好的解决办法是用pragma来指导编译器。Blackfin提供了指定不同的bank的pragma来告诉编译器数据来自不同的bank,从而可以更为激进的在同一个周期做两次数据访问。
片外存储器访问的速度取决于存储器的性能、速度以及处理器和内存间的总线宽度。在许多DSPs中,程序员可以选择存储器总线的速度。为了节省功耗,可以选择更为低速的存储设备,然是不弱你的程序对性能要求很高,你可能就需要选择更为高速的存储设备。一个硬件系统中,可以设定处理器频率和内存频率的分频比,即CPU的频率需要是内存频率的一个特定倍数,如图1是ADSP-BF533 Blackfin处理器的内存速度配置和CPU频率的比率关系,需要注意的是内存总线最大速率为133 MHz。
图 1. ADSP-BF533的CPU频率和内存频率配置
图2的分析可以看出外部存储器性能的重要性。第一排(L1)显示有single-cycle内存访问,第二行(L3缓存)是Cache缓存结果,第三行(L3)显示的是在一个un-cached系统,在这种情况下,连的16位传输需要带40个周期。值得注意的是,这里内存的访问的消耗代价将抵消任何优化的编译器带来的好处。外部存储器拥有一个结构行(也许在一排占据4 kB)和一个更大的结构的Bank。当存储访问从一行移动到下一个时,将需要增加一个额外的延迟,该延迟并不是很严重的性能损失,连续内存的访问很容易摊薄这个延时,而随即访问导致的连续变换的行讲导致非常严重的性能损失。
图 2. 不同情况下的内存访问周期数.
图2的一个自然的反应来可能依靠缓存cache来提高性能。显然缓存将内存访问的代价大大降低。但需要注意的是,顺序访问外部存储器的代价还是从L1缓存的7.7倍。如果你re-use重用数据,缓存数据的性能将大大提升。因此你应该考虑修改你的数据访问模式,尝试在循环内尽可能重用数据,而不是不断从外部存储器获取新的数据。这种cache的优化,编译器可以给你帮助不大。如在第一部分中所述你可以通过profiler来发现一个内存访问的代价过大的问题,大量的不明原因的load/store stall产生时,你需要重新思考你的内存使用情况。
代码的速度 vs 大小 Code Speed vs. Space
小程序有很多好处,代码size小了能放到L1片内存储空间,所以小代码可以提高应用程序的速度。较小的代码也许并不需要外部存储器,进而降低成本和能耗。编译工具通常会帮助你优化你的程序的大小。你可以要求优化的最大速度,您可以要求最低代码大小,或者你可以追求速度和空间的一种平衡。通常情况下,你可以给编译器一个低层次的指导来告诉编译器那些文件要求速度优先,哪些文件要求空间优先。图3显示的是不同的编译选项情况下选择速度优先和选择空间优先的代码空间变化。
图 3.不同优化选项下的代码大小变化
两个优化选项造成代码大小的重大区别。第一个是inline功能,那是有点惊讶,但同时也警告我们弄清楚你的编译器如何积极的追求速度不惜一切代价。"其他造成代码大小变化的是代码优化扩展,主要来自于对循环的优化。大量使用这些优化技术,如循环展开和流水线。最大化的利用16位数据偏移,而不是用32位的地址计算则有2%的增益。
一件需要注意的小事实是内存的边缘。程序员往往将数据数组在第一个地址或直到最后地址的数据。然而这导致了不利于软件流水的问题。软件流水是最强大的优化编译器方法,因为它允许DSP取下一个数据集的同时处理早先的数据。当一个数据数组被放在一个内存边缘时,最后一次循环的迭代将试图取位于内存空间之外的数据,这将引起一个地址错误。为了避免这个问题,编译器减少循环数,并把循环的最后一次迭代作为一个epilog迭代循环。这保证了代码的安全,但它同时也增加了许多指令代码。为了避免这个代码大小的增加, Blackfin有"-extra-loads "编译选项。这个选项告诉编译器是继续加载一个数组的元素师安全的。
现代的编译器尝试通过提供智能的空间和速度的优化。例如,Blackfin编译器提供了一个选择从0%提高到100%空间优化策略。编译器根据指定的目标来理解每个循环的空间优化。这个编译器也评估根据应用程序仿真的结果,对不经常执行的代码块采取空间优化策略,而对经常执行的代码块则是速度优化。因此编译器非常智能和灵活,下面来看编译器的表现。图4是一个测试程序,一个JPEG编码器,采取速度最大化优化和代码space优先的关系,我们可以在速度和空间二者中选择一个折中。
图 4. speed/size编译器的折中
如果你对全自动优化不感冒,或你不能用仿真的过程,你也可以手动处理这个问题。图5显示的是你对不同文件采取速度和空间优先编译时的影响。
图 5. 单独文件speed/size优化结果,%列显示的是该文件占整个优化程度的比例。如fileio.c占整个可能速度优化的24.95%
图5中,从"速度"一栏我们看到了不同速度和优化空间。只有黄色标示出的文件显示出显著的效果。这告诉我们我们大部分的应用可以被编译器以中立的态度来处理或者以节省空间策略处理。同样的右边栏目显示代码相同条件下大小的变化。只有用蓝色高亮的文件显示出显著的效果。唯一需要仔细考虑的文件是那些显示在两个黄色和蓝色。对许多人来说最好的优化设置是显而易见的。有趣的是,大多数黄色标示的文件和蓝色高亮的文件并不相符。这也说明一个小小的分析可以极大地减少错综复杂的优化选择。
Reference
http://en.wikipedia.org/wiki/Cache_algorithms
http://www.blog.163.com/houh-1984/
以上章节讲述了在ADI Blackfin系列上的C语言优化方法,如何利用DSP的特性进行循环和条件控制语句的具体优化策略,该部分则主要讲述如何优化memory的性能,以及如何实现速度speed vs代码大小size的折衷