DSP芯片的出现,是为了解决大量的数字运算问题。通过集成专用的加法器、乘法器、地址产生器、复杂逻辑等硬件单元,DSP能实现比普通单片机更快速的数字运算,使处理器更适用于实时性高、复杂度强的处理场合。也正因为如此,DSP编程中非常重要的一环就是让代码尽可能高效地运行。

本文基于TI C6000硬件架构,针对C语言编程,介绍其中主要的代码优化方法。

指导方针

在做优化前,当建立以下几点信念:

  • 循环最重要。显然,几乎所有耗时的运算都是在循环中进行,我们几乎可以说:代码的优化就是对循环的优化。

  • 最坏原则。TI CCS编译器集成有优化器,能对C/C++代码以及汇编代码进行性能优化。但是过度的优化可能会导致程序运行错误,因此在缺少信息的情况下,编译器总是抱着最坏的打算进行优化,以优先保证程序的正确性

  • 如果说TI提供的硬件架构和编译器优化工具是一把锤子,那编程者就应该让代码用起来像钉子。

  • 高度优化的c/c++代码性能可以非常接近手写的汇编代码。由于汇编代码的复杂性,我们可以优先选择C/C++进行程序编写。图1为TI提供的各编程语言及其优化版本间的运算性能比较示意图。

图1 各语言优化前后的性能对比

  • 优化并不是必须的,一般我们可以通过如图2所示的四个进阶的开发阶段,来判断对程序进行何种程度的优化。


图2 DSP 程序开发的一般过程

实用工具

如前面“锤子”与“钉子”的比喻,编程者的优化工作是尽可能地使代码能被处理器的功能单元、编译器等“锤子”充分运用,所以有必要先对它们建立基本的认识。

1. 高性能的C6000 DSP 架构

8个并行的功能单元,2组寄存器,分离的程序和数据存储;256bit取指包,能一次取指8个32bit的指令;2路64bit的数据加载/存储。这些都指向一个核心——并行处理。

图3 C6000 DSP 架构

2. C6000流水线

一个指令操作的完成实际上要经历取指、译码、执行三个阶段的多个过程才能实现,TI提供软件流水编排来让多个“工种”同时对多个操作进行流水式的处理,大大提高了运算的吞吐能力。

C64x+, C674x和 C66x系列内核还增加了软件流水循环缓存(SPLOOP buffer)单元,使得软件流水能更快速地加载数据,并可以被暂时打断。

However, the SPLOOP buffer cannot be used to handle loops that exceed 14 execute packets

For complex loops, such as nested loops, conditional branches inside loops, and function calls inside loops, the effectiveness of the compiler may be compromised.

3. SIMD(Single Instruction, Multiple Data)

C6000支持单指令多数据存取,仅一条指令,就能一次性操作64bit的数据,这64bit可以由多个双字/字/字节数据组成。

4. C6000 C Compiler

编译器是代码优化工作的最终执行者,它分析代码的相关信息,并做出优化的决策。但有时,编译器无法仅通过分析代码获得一些对于优化很重要的信息,这时编程者主动提供必要的信息给编译器就显得非常重要。

我们可以通过编译选项、关键词以及pragma编译指示来告知编译器和优化有关的信息。同时,还可以利用编译器的优化返回信息,进一步调整优化策略。

5. 其它

  • 内嵌函数

TI提供一套内嵌函数供编程者调用,内嵌函数由一些特定的指令组成,配合DSP芯片的硬件函数功能单元,能高效地完成一些用C语言很难完成的复杂操作。

The intrinsic operations are not function calls (though they have the appearance of function calls), so no branching is needed.Instead, the use of intrinsic is a way to tell the compiler to issue one or more particular instructions of the C6000 instruction set.

  • 优化的库函数

TI也将一些通用的运算模块封装成库函数,这些库函数都经过了深度的优化,能特别高效地完成相应的运算。

优化策略

1. 选择合适的编译选项(介绍部分)

  • -o0/1/2/3:最重要的优化选项;如果选择了-o3,编译器将会尽可能尝试所有可行的优化手段,但有时也可能会使得优化后的程序出现错误。-o0和-o1将不会产生优化错误,但优化的性能则大大降低。
  • -g:允许编译器插入符号调试信息,它在开发调试阶段是非常好的工具,但在最终产品代码编译中应避免使用,因为它会减少并行处理指令,并占用额外的代码空间,极大地影响代码性能。
  • -mt:指示编译器,应用中所有的函数中的指针参数都不指向同一个地址,但它只作用于函数参数中的指针,对函数内部的本地指针无效。
  • -k和-mw:这两个选项与最终代码性能无关,但利用他们可以获得编译器的优化反馈信息,帮助编程者调整优化策略。-k选项保留编译器的汇编输出;-mw输出软件流水的相关信息

2.“restrict”关键字

restrict的作用和-mt编译选项相似,它告诉编译器某个指针不会和函数中的其它指针指向同一个内存。

the restrict keyword can be applied to any pointers, regardless of whether it is a parameter, a local variable, or an element of a referenced dataobject. Moreover, because the restrict keyword is only effective in the function it is in, it can be used whenever needed

注意:“restrict”关键字也不能随便乱加,我们需要了解C6000的片上内存组成,只有当两个指针所指的内存在不同的block里时,restrict才是合法的。

3. 通过编译指示(pragma)提供信息

可以在代码中插入一些特定语法的编译指示指令,来告知编译器有关代码的一些信息。因为编译器在缺少信息的情况下,总是以最坏的打算来优化代码,如果编程者能提供一些关键信息,会大大帮助编译器做出好的决策。最常用的有MUST_ITERATE和UNROLL两种。

  • MUST_ITERATE:提供关于循环次数的一些确切信息:最小可能循环次数、最大可能循环次数以及循环次数为某个factor的倍数。它的使用语法如下:

  • UNROLL:展开指示告诉编译器可以对循环代码进行适当的展开,其使用语法如下:

在展开指示之前,也最好用MUST_ITERATE指令告诉编译器循环次数为展开系数的倍数,这样可以避免产生额外的代码来处理异常情况,如:

展开的好处有两点:一是使得编译器可以更加均衡地利用各运算单元;二是编译器有更多的机会使用SIMD指令。但有一点需要注意的是:展开将会使得循环体增大。

4. 循环体优化的注意事项

循环的优化关键在于使得循环能够被编排成软件流水。

For complex loops, such as nested loops, conditional branches inside loops, and function calls inside loops, the effectiveness of the compiler may be compromised. When the situation becomes too complex, the compiler might not be able to pipeline at all.

  • 编译器仅对内部循环执行软件流水。

  • 软件流水循环可包含instrinsics,但不能包含函数调用。

  • 循环结构中不可有break和goto语句,不可有条件中止,使循环提前退出的指令。

  • 条件代码应尽量简单,在C64XX中,条件代码需要超过6个寄存器时,循环不可进行软件流水。

  • 避免循环体内容过于复杂,造成寄存器组不够用。

  • 如果要求一个寄存器的生命太长,这个代码不能进行软件流水。

  • 循环结构中不要包含改变循环计数器数值的代码。

5. 使用SIMD并行处理多个数据的运算

TI 提供了多个支持SIMD的指令,如LDDW、STDW,分别用于并行64bit的数据加载和存储。

Assuming all the data used are actually 16-bit data, it would be ideal if the LDDW and STDW instructions can operate on 4 elements every time. However, if thedata is declared as 32-bit type, LDDW and STDW can only operate on 2 elements every time. Therefore,to fully utilize SIMD instructions, it is important to choose the smallest data size that works for the data.

尽管C64x+, C674x, C66x内核支持对非对齐的数据使用SIMD指令,但当数据量比较大时,还是应该尽量保证数据是边界对齐的,这样可以充分地进行并行存取。

6. 尽可能使用内嵌函数

内嵌函数来直接调用C6000的汇编操作,而这些操作往往用C语言实现很复杂。

The intrinsic operations are not function calls (though they have the appearance of function calls), so no branching is needed.Instead, the use of intrinsic is a way to tell the compiler to issue one or more particular instructions of the C6000 instruction set.

7. TI库函数

针对常见的运算,TI提供了高效的实现库函数,包含基础的通用模块如图4。当然,针对一些专用领域(如图像视频、通信等),也会有对应的库函数,这些库函数都经过了深度的优化,运行是非常高效率的。


图 4  TI基础库函数类别

8. 使用内联函数代替函数调用

内联函数(Inline Function)是C语言语法的一种,它的定义类似于普通的函数定义,但编译器在处理它的时候并不把它当函数对待,而是把它的函数体自动嵌入到被调用处。

由于在循环体中,函数调用将会影响到软件流水的编排,并且产生调用开销,用内联函数代替函数调用是个不错的选择。

但是注意:内联函数将可能增加代码的尺寸,应避免内联函数体过大或被频繁调用。

9. 尽可能使用逻辑运算代替乘除运算

乘除运算指令的执行时间要远远超过逻辑移位指令,尤其是除法指令,在设计的时候,可以根据实际情况,进行一些调整,尽量用逻辑移位运算来代替乘除运算,这样可以加快指令的运行时间。

参考文献/资料

【1】Introduction to TMS320C6000 DSP Optimization--SPRABF2,2011.

【2】TMS320C6000 Programmer's Guide--SPRU198K,2011.

【3】董言治, 娄树理, 刘松涛. TMS320C6000系列DSP系统结构原理与应用教程[M]. 清华大学出版社, 2014.

【4】TI C6000 优化 startup guide.

【5】Optimization Techniques for the TI C6000 Compiler.

【6】Optimizing Modems Using Code Composer Studio and TI Resources.

·END·

欢迎来我的微信公众号做客:信号君

专注于信号处理知识、高性能计算、现代处理器&计算机体系

技术成长 | 读书笔记 | 认知升级

幸会~

TI C6000优化手册——让代码看起来像钉子的更多相关文章

  1. TI C6000 优化进阶:循环最重要!

    软件流水循环 1. C6000流水线(Pipeline) 一个指令的处理过程并不是一步完成,它被分为三个阶段:取指(Fetch).译码(Decode).执行(Excute).将每一个阶段放入独立的流程 ...

  2. Java 性能优化手册 — 提高 Java 代码性能的各种技巧

    转载: Java 性能优化手册 - 提高 Java 代码性能的各种技巧 Java 6,7,8 中的 String.intern - 字符串池 这篇文章将要讨论 Java 6 中是如何实现 String ...

  3. TI C6000 数据存储处理与性能优化

    存储器之于CPU好比仓库之于车间.车间加工过程中的原材料.半成品.成品等均需入出仓库,生产效率再快,如果仓库周转不善,也必然造成生产阻塞.如同仓库需要合理地规划管理一般,数据存储也需要恰当的处理技巧来 ...

  4. MySQL通用优化手册

    转载: MySQL通用优化手册 内容提纲 MySQL的特点: 硬件.系统优化: MySQL 配置优化: SCHEMA设计优化: SQL 优化: 其他优化. MySQL 的特点 首先,需要明确的是.想要 ...

  5. CssStats – 分析和优化网站 CSS 代码的利器

    CssStats 是一个在线的 CSS 代码分析工具,你只需要输入网址或者直接 CSS 地址即可进行 CSS 代码的全方位分析,是前端开发人员和网页设计师分析网站 CSS 代码的利器,可以统计出 CS ...

  6. 好看的IDE配色方案让代码看起来不再那么凶猛了

    写这篇小文的初衷是,笔者是原教旨主义者,一直坚持用IDE默认的配色方案.另外也觉得网上黑色系的配色方案太过bling bling了.但今天尝试用新的配色方案后,兴奋地发现对代码的好感度大幅提升. 嗯, ...

  7. 优化C/C++代码的小技巧

    说明: 无意看到一篇小短文,猜测作者应该是一个图形学领域的程序员或专家,介绍了在光线(射线)追踪程序中是如何优化C/C++代码的.倒也有一些参考意义,当然有的地方我并不赞同或者说我也不完全理解,原文在 ...

  8. 优化C/C++代码的小技巧(转)

    源:http://www.cnblogs.com/lizhenghn/p/3969531.html 说明: 无意看到一篇小短文,猜测作者应该是一个图形学领域的程序员或专家,介绍了在光线(射线)追踪程序 ...

  9. 《阿里巴巴Java开发手册》代码格式部分应用——idea中checkstyle的使用教程

    <阿里巴巴Java开发手册>代码格式部分应用--idea中checkstyle的使用教程 1.<阿里巴巴Java开发手册> 这是阿里巴巴工程师送给各位软件工程师的宝典,就像开车 ...

随机推荐

  1. Unity3d开发的第一个实例

    1.untiy3d开发环境配置好以后,开始我的第一个开发实例 2.在Hirearch---create---3DObject---Cube,在场景中创建一个正方体 3.project---create ...

  2. iOS 收藏的笔记

    目录 UI 资料类 网络篇 图表 动画 菜单栏 数据存储和数据库 第三方库 社交分享 刷新 视频音频 其他 阅读 JS 导航 系统 支付 书籍 工具类 完整项目收集 DEMO UI http://ww ...

  3. 安卓usb数据接收

    之前在论坛里面求助了关于监听数据接收的问题,因为第一次做这方面,可能我提的问题太简单了,大神都不愿意回答我,(之前的帖子)晚上FQ浏览网站发现问题的解决办法, 原文是:最近老板让弄安卓和一块板子通信, ...

  4. vue3.0学习笔记(二)

    一.选择合适的ide 推荐使用vs code编辑器,界面清晰.使用方便,控制台功能很好用.webstorm也可以,看个人喜好. 二.ui框架选择 目前,pc端一般是选择element ui(饿了么), ...

  5. hibernate课程 初探单表映射1-6 hibernate项目建立以及导入jar包

    hibernate 项目建立 1 new ==>java project hibernate 导入jar包 1 windows==>prerence==>java ==>bui ...

  6. 从零开始的全栈工程师——js篇2.13(字符串与数组的方法)

    基类Object的子类有 Function  Array  Number  Boolean  String  Date  Math  RegExp 函数 数组 数字 布尔 字符串 日期 算数 正则 都 ...

  7. The sixth day

    bound to 铁定You are bound to be fired  你会被铁定开除的 A:Dan forgot his map? Dan忘了带地图了吗? B:Yep!And he's boun ...

  8. 如何避开JavaScript浮点数计算精度问题(如0.1+0.2!==0.3)

    不知道大家在使用JS的过程中有没有发现某些浮点数运算的时候,得到的结果存在精度问题:比如0.1 + 0.2 = 0.30000000000000004以及7 * 0.8 = 5.60000000000 ...

  9. LeetCode Longest Substring Without Repeating Characters 最长不重复子串

    题意:给一字符串,求一个子串的长度,该子串满足所有字符都不重复.字符可能包含标点之类的,不仅仅是字母.按ASCII码算,就有2^8=128个. 思路:从左到右扫每个字符,判断该字符距离上一次出现的距离 ...

  10. 基于PowerShell的Lync Server管理 使用C#

    这里所说的Lync Server管理,指通过C#管理Lync账号的启用,禁用,开启账户的语音功能. Lync服务器安装后,会自动创建一个用于远程管理的应用程序,通过IIS查看,其应用程序名为: Lyn ...