TI C6000 优化进阶:循环最重要!
软件流水循环
1. C6000流水线(Pipeline)
一个指令的处理过程并不是一步完成,它被分为三个阶段:取指(Fetch)、译码(Decode)、执行(Excute)。将每一个阶段放入独立的流程车间处理,形成流水线式的处理过程,可以大大加快指令的处理速度。
如图1所示,流水编排后的3个指令只需5个cycle,相比顺序执行的9个cycle大大减少,当指令数增多,流水线的优势将更加明显。
图1 简单的流水线编排示意
实际上,C6000架构把每一个阶段进一步划分为多个子阶段,每个子阶段消耗1个CPU cycle。
取指(4个子阶段):
PG: Program address generate (update program counter register)
PS: Program address send (to memory)
PW: Program (memory) access ready wait
PR: Program fetch packet receive (fetch packet = eight 32-bit instructions)
译码(2个子阶段):
DP: Instruction dispatch (or assign, to the functional units)
DC: Instruction decode
执行(1-10个子阶段,指令间有区别):
E1 – E10, where E1 is the first sub stage in the execute stage
图2 高性能的C6000流水线
2. 流水线阻塞
下列两种情况出现时,流水线将被阻塞:
当前为load、complex multiply等具有多个延时时隙(delay slot)的指令时,下一指令需多个cycle,等其返回结果后才能往下继续执行
跳转指令出现时,CPU无法预知下一步执行哪个分支指令,因此跳转目标指令需等到跳转指令执行到E1阶段才能进入流水线
为了充分利用流水线的资源,避免delay slots造成的阻塞,C6000架构在软件和硬件上分别新增了一个处理机制:
软件上:提供软件流水(software pipelining)指令编排
硬件上:提供SPLOOP buffer(software pipelining loop buffer)
3. 软件流水
软件流水≠流水线!
软件流水技术指编译器通过重新调整指令的位置,使得原本将发生阻塞的流水线得以充分利用,其重点在“软件”两字。
例如处理下面循环:
for(i=0; i<15; i++)
{
sum += tab[i];
}
传统指令流程(Solution1)和软件流水编排后的指令流程(Solution2)如图3所示:
图3 传统编排 Vs 软件流水编排
从图中看到,经过软件重新编排过的指令将不再发生流水线阻塞,提升运行效率的同时也不影响代码功能的实现。
解释下有关软件流水的三个名词:
流水核(Kernel):流水线被充分利用的一段代码
流水填充(Prolog):流水核之前的一段填充过程代码
流水排空(Epilog):流水核之后的一段排空过程代码
4. SPLOOP Buffer
软件流水将会带来两个主要的缺点:汇编文件代码尺寸的增加并影响代码的中断属性。
SPLOOP Buffer的出现就是为了就解决以上问题。SPLOOP Buffer是C6000内部的一个存储区域,用来装载SPLOOP指令。当一个SPLOOP 第一次被执行时,循环的相关指令被拷贝到SPLOOP Buffer中,整个循环运行过程将从这里取指执行,直到循环结束。
C6000还为SPLOOP Buffer的使用提供了专门的寄存器和操作指令,如果编程者使用汇编/线性汇编编程,需要熟悉这些指令和寄存器,并了解SPLOOP Buffer特有的执行机制(参考文献[1]),如果使用C/C++编程则由编译器自动生成相应指令。
以存储块拷贝函数为例,示意使用SPLOOP Buffer前后的编码效果:
图4 memery copy 使用SPLOOP buffer前
图5 memery copy 使用SPLOOP buffer后
*注意:SPLOOP Buffer最多只能存放14个执行包(每个执行包可含8条指令,顺序/并行),因此如果循环体较复杂将不能使用SPLOOP Buffer。
5. 导致软件流水编排失败的因素
在CCS开发环境中,开启-O2/-O3优化选项,编译器将自动为合适的代码进行软件流水编排,因此编程者需要注意的是使设计的循环体符合软件流水编排的条件。
下列一些因素将可能引起软件流水编排失败:
汇编语句嵌入到C/C++代码中
出现复杂的流控制语句如goto、break等
循环中包含一个调用(内嵌函数除外)
需进行软件流水编排的指令太多
没有初始化循环计数器
循环变量在循环过程中被修改
软件流水被关闭:没有使用-O2或-O3选项;使用了-ms2或-ms3选项;使用-mu关闭了软件流水
理解编译器反馈信息
1. 编译器优化循环的几个步骤
循环的运算性能主要取决于编译器能否编排出恰当的软件流水,编译器优化一个循环的过程大致分为三个步骤:
获取循环次数信息。这些信息能帮助编译器判断是否要对循环做自动展开等操作。有时编译器无法从代码中获得完整的这些信息,编译器将会对循环采取保守的优化策略。因此,若要获得最佳的优化性能,编程者应尽可能地提供这些信息给编译器,可通过 MUST_ITERATE 、UNROLL 等 pragma语句。
几个关键参数如下:
最小可能循环次数(Minimum Trip Count)
最大可能循环次数(Maximum Trip Count)
循环倍数系数(Max Trip Count Factor)
收集循环资源和相关图信息。CPU完成一次循环迭代所需的cycle数称为迭代间隔(iteration interval,ii),编译器的优化目标就是最小化ii。
几个关键参数如下:
循环执行相关限(Loop Carried Dependency Bound),指循环体中最大的一条依赖路径的距离,而所谓依赖是指当前指令的开始依赖于前面指令的结束。
以下面一段代码为例:
void simple_sum(short *sum, short *in1, unsigned int N)
{
int i;
for (i = 0; i < N; i++)
{
sum[i] = in1[i] + 1;
}
}
其中最大的依赖路径如下图所示,图中显示,下一次数据的加载需等待上一次数据存储完成。
图6 循环依赖路径
很多时候,循环执行相关限是由于编译器对某些指针变量的信息缺乏了解造成,当指针的确切值无法得知时,编译器必须假定任何两个指针都可能指向相同的位置。因此,从一个指针进行加载暗含了其与另一个指针执行存储操作的相关,反之亦然。类似这种相关通常是不必要的,编程者可通过添加“restrict”关键词来消除这种相关。
未分配资源限(Unpartitioned Resource Bound):编译器将每条指令分配到A、B运算单元之前,使ii达到最小情况下的资源限。
已分配资源限(Partitioned Resource Bound):编译器将每条指令分配到A、B运算单元之后,使ii达到最小情况下的资源限。
寻找软件流水编排策略。编译器首先将ii设为Loop Carried Dependency Bound和Partitioned Resource Bound两个指标中的大值,然后以它为目标寻找编排策略。如果失败,则将ii+1,继续寻找……期间编译器将会反馈出编排失败的原因和一些相关信息。
2. 编译反馈输出相关选项
编程者可以选择查看上述编译器反馈的一些编译信息,来了解代码的优化程度,进而相应调整代码结构。如果要获得反馈输出,需打开-k和-mw选项。
-k:保留编译输出的汇编文件
-mw:生成详细的软件流水报告
一个反馈信息的例子如下所示:
图7 编译器反馈信息示例
3. 依据反馈信息制定优化策略
Loop Carried Dependency Bound is Much Larger Than Unpartitioned Resource Bound
现象描述:循环执行相关限远大于未分配资源限。
分析:相关路径过长,可能存在潜在的存储器别名混淆问题。
解决方案:
运用-pm程序及优化减少存储器指针别名
对传入函数且没有存储重叠的指针参数加“restrict”
运用-mt选项假定没有存储器别名问题
使用.mdep和.no_mdep汇编优化指令
如果循环控制复杂,尝试用-mh选项
Uneven Resources
现象描述:对某一运算单元的使用次数为奇数。
分析:如一次循环过程调用了3次乘法,那么分配到A、B两侧后,执行这个循环至少需要2个迭代间隔。如果能将该循环展开一倍,则循环一次需6次乘法,分配到A、B两侧后得到一个3周期的ii,从而改善循环性能。
解决方案:展开循环从而得到偶数个资源。
Larger Outer Loop Overhead in Nested Loop
现象描述:存在循环嵌套时,外循环所用时间占整个循环时间的比重很大。
分析:内循环计数值较小,而编译器将只重点优化内循环。
解决方案:展开内层循环,使嵌套结构变为一个大循环。
T Address Paths Are Resource Bound
现象描述:T地址通道数定义为每次循环迭代从地址总线发出的存储器访问次数,该资源的访问对循环进行有限制。
分析:应减少对T地址通道的访问次数。
解决方案:用宽字节存取指令访问存储器。
冗余循环
1. 什么是冗余循环?
为了填充流水线,软件流水循环结构需要循环迭代至少执行某个次数(循环次数要达到最小循环计数值)。当编译器无法确定循环计数时,默认会产生两个循环:一是不经流水编排的代码,二是循环流水编排的。运行时,若循环计数大于等于最小循环计数值,执行第2个循环,否则执行第1个。因此,总有一个循环不被执行,这个循环就是冗余循环。
冗余循环的出现将引起代码尺寸的增加。
2. 如何避免生成冗余循环
设置-ms选项的任一级别都将关闭冗余循环的生成
使用MUST_ITERATE pragma伪指令告诉编译器循环次数的具体信息
中断对循环的影响
1. 单分配和多分配
C6000中的寄存器分配被分为单分配和多分配。单分配代码是可中断的,多分配代码是不可中断的。
多分配是指某一特殊寄存器被分配多于一个值。如下为一段多分配的代码。
cycle
1 SUB .S1 A4,A5,A1 ; writes to A1 in single cycle
2 LDW .D1 *A0,A1 ; writes to A1 after 4 delay slots
3 NOP
4 ADD .L1 A1,A2,A3 ; uses old A1 (result of SUB)
5−6 NOP 2
7 MPY .M1 A1,A4,A5 ; uses new A1 (result of LDW)
在第4周期,ADD指令开始时,寄存器被分配两个不同值。一个值是SUB指令在第1周期所写,已经在寄存器中存在。第二个值叫做in-flight值,在第2周期由LDW指令分配。因为LDW指令在第6周期之前没有实际值写入寄存器A1,所以考虑分配为in-flight。
in-flight操作因其不可预见性所以其代码不可中断。
2. 循环不可被中断的情形
所有小于6个cycle的循环都是不可中断的。
即使代码使用单分配,但循环也可能不能使用中断。因为硬件中断保护所有分值操作的延迟间隙,所以只要CPU有悬挂分支,所有中断就保持悬挂。而又因为分支跳转指令有5个延迟时隙,小于6个cycle的循环总有悬挂分支。
3. 如何设置循环体的中断属性?
循环的中断属性可设置为三个级别:
级别0,不可中断
该级别下编译器不能使中断无效,因此需确保中断不会发生,其优势是允许编译器用多分配代码以及产生最小周期间隔的软件流水循环。
-mi选项将某一模块设置为该属性
可用pragma伪指令对某个特殊函数func进行如下设置:
#pragma FUNC_INTERRUPT_THRESHOLD(func, uint_max);
级别1,任何时刻可中断
该级别编译器处处用单分配,绝不产生小于6个cycle的循环。
-mi1选项将某一模块设置为该属性
可用pragma伪指令对某个特殊函数func进行如下设置:
#pragma FUNC_INTERRUPT_THRESHOLD(func, 1);
级别2,在阈值周期内不可中断
用户设置一个周期阈值threshold,若循环的cycle数未超过阈值,编译器将在循环周围关中断,并允许寄存器多分配。反之,则产生单分配的代码,循环可中断。如果编译器无法判断循环周期,则假定达到阈值,产生一个可中断循环。
-mi(阈值)选项将某一模块设置为该属性
可用pragma伪指令对某个特殊函数func进行如下设置:
#pragma FUNC_INTERRUPT_THRESHOLD(func, threshold);
参考文献
【1】Introduction to TMS320C6000 DSP Optimization--SPRABF2,2011.
【2】TMS320C6000 Programmer's Guide--SPRU198K,2011.
【3】田黎育,何佩琨,朱梦宇. TMS320C6000系列DSP编程工具与指南[M].北京:清华大学出版社 2006.
·END·
欢迎来我的微信公众号做客:信号君
专注于信号处理知识、高性能计算、现代处理器&计算机体系
技术成长 | 读书笔记 | 认知升级
幸会~
TI C6000 优化进阶:循环最重要!的更多相关文章
- TI C6000优化手册——让代码看起来像钉子
DSP芯片的出现,是为了解决大量的数字运算问题.通过集成专用的加法器.乘法器.地址产生器.复杂逻辑等硬件单元,DSP能实现比普通单片机更快速的数字运算,使处理器更适用于实时性高.复杂度强的处理场合.也 ...
- TI C6000 数据存储处理与性能优化
存储器之于CPU好比仓库之于车间.车间加工过程中的原材料.半成品.成品等均需入出仓库,生产效率再快,如果仓库周转不善,也必然造成生产阻塞.如同仓库需要合理地规划管理一般,数据存储也需要恰当的处理技巧来 ...
- GPU 编程入门到精通(五)之 GPU 程序优化进阶
博主因为工作其中的须要,開始学习 GPU 上面的编程,主要涉及到的是基于 GPU 的深度学习方面的知识.鉴于之前没有接触过 GPU 编程.因此在这里特地学习一下 GPU 上面的编程. 有志同道合的小伙 ...
- ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase
原文地址:http://www.51csharp.com/MVC/882.html ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL 引言-- 在初级篇中,我们 ...
- Oracle SQL优化进阶学习
引言 对于下面的Oracle分页如何优化该段语句: SELECT * FROM (SELECT A.*, ROWNUM RN FROM (SELECT * FROM task_log order by ...
- 【ABAP系列】SAP ABAP 优化LOOP循环的一点点建议
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP 优化LOOP循 ...
- ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL
http://www.cnblogs.com/John-Connor/archive/2012/05/03/2478821.html 引言-- 在初级篇中,我们介绍了如何利用基于ASP.NET MVC ...
- [转载]ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL
引言-- 在初级篇中,我们介绍了如何利用基于ASP.NET MVC的Web程序中的Global文件来简单的重写路由.也介绍了它本身的局限性-依赖于路由信息中的键值对: 如果键值对中没有的值,我们无法将 ...
- [转]ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL
本文转自:http://www.cnblogs.com/John-Connor/archive/2012/05/03/2478821.html 引言-- 在初级篇中,我们介绍了如何利用基于ASP.NE ...
随机推荐
- OpenStack Ocata Telemetry 警告服务部署
下列操作在控制节点上进行: 1 准备条件 在配置OpenStack Telemetry服务之前,你必须创建数据库.服务凭证和API端点. 1.1 数据库 以root用户连接数据库服务器,创建glanc ...
- notepad++ 等用正则表达式自动添加sql引号(宏)
一般sql语句会经常用到给括号里的内容添加引号,sql如下 Select * From Test ', ', ', ', ', '); 一开始参考了http://blog.sina.com.cn/s/ ...
- System Center Configuration Manager 2016 域准备篇(Part3)
步骤2.将CM16加入域 注意:在ConfigMgr服务器(CM16 )上以本地管理员身份执行以下操作 手动加入域,请登录CM16.启动Windows文件资源管理器 右键单击This-PC,然后选择 ...
- 删除elasticsearch大于7天前的索引
curl -u 用户名:密码 -H'Content-Type:application/json' -d'{ "query": { "range": { &quo ...
- oracle数据类型及操作
1. Oracle字符串操作 1.1 字符串类型 Ø CHAR和VARCHAR2类型 l CHAR存放定长字符,如果数据存不满指定长度则用空格补齐,CHAR类型浪费空间换取查询时间的缩短. l VAR ...
- 写在Github被微软收购之际 - Github的那些另类用法
这几天朋友圈被微软75亿美元收购Github的新闻刷屏了.Jerry也来贡献一篇和Github相关的文章. 这篇文章包含了Jerry平时对于Github的一些另类用法.目录如下: 1. 部署HTML应 ...
- 使用Java+SAP云平台+SAP Cloud Connector调用ABAP On-Premise系统里的函数
最近Jerry接到一个原型开发的任务,需要在微信里调用ABAP On Premise系统(SAP CRM On-Premise)里的某些函数.具体场景和我之前的公众号文章 Cloud for Cust ...
- SRM 670 div2 A B C div1 A(贪心,子问题合并)
A Cdgame brute force... B Drbalance 贪心,每次选最前面的-变成+,相当于后面所有的负值+2. C Treestrat 考虑集中去抓一个Red Token,以这个To ...
- 【转】IOS基础:深入理解Objective-c中@class的含义
objective-c中,当一个类使用到另一个类时,并且在类的头文件中需要创建被引用的指针时, 如下面代码: A.h文件 #import "B.h" @interface A : ...
- 【转】CentOS 7.0 安装Redis 3.2.1详细过程和使用常见问题
http://www.linuxidc.com/Linux/2016-09/135071.htm 环境:CentOS 7.0 Redis 3.2.1 Redis的安装与启动 这里我把Redis放在/h ...