This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

【初学者请进入】TI C6000 优化 startup guide

TI有很多介绍C6000优化方法的文档。本文列举了其中最关键的4个文档。

并用中文注释了每个文档重点章节的知识点。可以作为初学C6000优化的参考。

1      学习TI C6000 DSP 优化的资料

下面列举的C66优化学习资料按重要程度排序。

1.1    TMS320C6000 Programmer’s Guide

文档下载链接http://www.ti.com/lit/ug/spru198k/spru198k.pdf

TI有一个很经典的介绍C优化的文档,可以作为学习C6000优化的教科书。

TMS320C6000 Programmer’s Guide (spru198K)

这里是文档目录

这个文档的章节2,3,4需要仔细琢磨。关键内容大约90页。下面列出了每个章节的导读。对初学着较有帮助。

重点章节

Page

导读

2. Optimizing C/C++ Code

 

 

2.1 Writing C/C++ Code

2

 

2.2 Compiling C/C++ Code

 

 

2.2.1 Compiler options

3

介绍了重要的编译选项 -g, -o3, -k -mw, -mi, -mh, -pm

2.2.2 Memory Dependencies

4

介绍了什么是memory dependency以及使用restrict

2.3 Profiling Your Code

2

这部分有些过时了,可以不看。TI local FAE开发了一个很好的profiling工具,采用function hook的办法可以profiling函数的执行次数和消耗的cycle数。需要6.1版本以上的编译器才能支持。

2.4 Refining C/C++ Code

 

 

2.4.1 Using Intrinsics

14

介绍使用intrinsic 优化代码,有intrinsic的列表。不过仅限于C64+,其实除了load/store的操作之外,intrinsic基本和DSP指令是一一对应的,所以可以参考TMS320C66x DSP CPU and Instruction Set Reference Guide sprugh7.pdf,较全的intrinsic可以在..\compiler\c6000\include\c6x.h中找到,

2.4.2 Wide Memory Access for Smaller Data Widths

18

举例说明怎样最大限度利用DSP D单元的存取位宽 (64bits)。 包括使用_nassert()和MUST_ITERATE pragma等

2.4.3 Software Pipelining

12

比较重要的几个章节如下,介绍了几个关键的预编译指令。内容顾名思义

2.4.3.1 Trip Count Issues  解释了什么是softare pipeline的minimum safe trip count。

2.4.3.2 Eliminating Redundant Loops, 在循环的最少循环次数未知时,编译器会生冗余的循环。介绍了什么是冗余循环以及怎样消除它。
2.4.3.3 Communicating Trip-Count Information to the Compiler
2.4.3.4 Loop Unrolling
2.4.3.5 Speculative Execution (?mh option)
2.4.3.6 What Disqualifies a Loop from Being Software-Pipelined

 

 

 

3 Compiler Optimization Tutorial

 

 

3.1 Introduction: Simple C Tuning

 

 

3.2 Lesson 1: Loop Carry Path From Memory Pointers

8

demo了什么是loop carry dependency,以及怎样消除dependency。这个例子在消除dependency后可以使循环cycle数减少到原来的1/5。loop carry dependency是导致代码抵消的最普遍的原因。在循环中如果有对两个以上数组读写,基本都会存在loop carry dependency的问题

3.3 Lesson 2: Balancing Resources With Dual-Data Paths

5

由于C64,C64+,c66的core都是分成A,B两套datapayh和register file的,所以balance resource 也很重要。这一节用例子demo了怎样加#pragma MUST_ITERATE使循环效率提高了1.5倍

3.4 Lesson 3: Packed Data Optimization of Memory Bandwidth

5

由于DSP的D单元的最大存取宽度是64bit,但是通常的操作数最大就是32bit。为了充分利用D单元,可以用预编译指令 (_nassert(((int)(x) & 0x3) == 0))告诉编译器数组的对齐特性。编译器在优化时就会尽量使用一次load/store 64bit的宽存取指令。如果编译器的优化效果不好还可以手工使用intrinsic _amem8()或 _mem8()一次load多个操作数。这一节就demo了这样一个例子。优化后循环效率提高了1.5倍。

3.5 Lesson 4: Program Level Optimization

2

一般编译器是以文件为单位做优化的。启用program level优化后,编译器会对整个工程的source code combine到一起生成一个中间文件。Program level优化主要从以下几个方面改进优化效果。

a)    If a particular argument in a function always has the same value, the compiler replaces the argument with the value and passes the value instead of the argument.

b)    If a return value of a function is never used, the compiler deletes the return code in the function.

c)    If a function is not called, directly or indirectly, the compiler removes the function.

d)    Also, using the −pm option can lead to better schedules for your loops. If the number of iterations of a loop is determined by a value passed into the function, and the compiler can determine what that value is from the caller, then the compiler will have more information about the minimum trip count of the loop leading to a better resulting schedule.

3.6 Lesson 5: Writing Linear Assembly

 

可以略过,这种优化方法目前基本不使用

 

 

 

4. Feedback[1]Solutions

22

这一章非常重要,介绍了怎样理解编译器的反馈信息。
编译器在优化时是着力于代码中的循环的,因为循环最消耗cycle。
编译器能输出asm文件,asm文件中有每个循环优化后的pipeline信息。读懂这些信息能指导我们消除瓶颈,进一步提升循环的效率。
优化其实是个反复调整的过程:
 代码优化->编译->读编译反馈的pipeline信息->下一轮代码优化->...
有经验的工程师看了asm文件中有每个循环优化后的pipeline信息后就可以明确 哪些循环已经达到优化极限,那些还有提升空间。这样可以做到有的放矢。

 

 

 

5. Optimizing Assembly Code via Linear Assembly

 

可以略过,这种优化方法目前基本不使用

注意,spru198中提到downcounting loops更有利于优化,这可能是早期编译器版本的一个限制。根据我们的经验,现在的编译器对优化increasing loop和downcounting loops效果是一样的。所以没有必要刻意的把increasing loop改成downcounting loops。

 

通过学习这个文档,就能掌握C6000优化的基本方法。不过优化是需要经验积累的。尤其是DSP的几百条指令必须经过一段时间的实践才能做到活学活用。C6000有很多SIMD指令,以及配合SIMD操作的数据pack,unpack指令。

从C64+开始出现了SPLOOP,这是一个专用的硬件loop buffer,可以把code size较小的循环装到loop buffer,就不需要重复的从指令cache取代码。loop buffer最大的好处是减小code size(说来话长,要从编译器通过展开和叠卷循环代码的多次iteration说起,这里不赘述)。而且使用loop buffer的循环可以被中断打断。至于SPLOOP的专用标示指令不需要深究。一般的优化也不需要了解那么细。

1.2    Optimizing Loops on the C66x DSP( SPRABG7)

文档下载链接http://www.ti.com/lit/an/sprabg7/sprabg7.pdf

这个文档针对C66的特性对C66特有的优化方法做了介绍,有很多实例。对学习C66优化比较重要。C66相对C64+的变化如下,做C66优化的关键是扬长避短。

改进

ü  增加了128bit位宽的数据类型__x128_t,因为很多SIMD指令的操作数是128bit

ü  增加了浮点指令,包括:加减乘,复乘指令和浮点求倒数,定浮点转换等。浮点指令的引入使得信号链中的不算算法不再需要定点化。加快了开发进度。不过总的来说C66的定点计算能力比浮点计算能力还是强2~3倍,所以,运算最密集的地方还是需要用定点算法实现。根据现有经验,在基带处理中使用浮点运算最有效的地方是矩阵求逆和替换除法。

(1) 矩阵求逆。浮点指令用于矩阵求逆也很有效。

由于浮点计算精度高,所以一些简单的算法(例如blockwise)也可以用来做大size(例如4×4,8×8)矩阵的求逆

(2) 替换除法。原来在C64+上实现除法使用SUBC指令以为相减来做。

在C66上可以按下面的流程: 假设要计算a/b那么:

a,b定点转浮点 -> 用求倒指令rcpsp计算1/b –> 1/b做牛顿插值 -> 浮点计算a/b并做scaling -> 浮点转定点

关于牛顿插值:因为rcpsp的结果只有8比特精度,所以要通过牛顿插值提高精度,每次插值能提高8比特精度,一般做一次就可以了).插值公式是:x[n + 1] = x[n](2 - v × x[n]), v是原值, x[#]是迭代的结果。

需要注意的是如果循环中的除法,做上述替代比较有效。如果是单次触发则替换的意义不大。因为浮点指令的delay比较长。

ü  增加了复数矩阵乘法指令(CCMATMPY),一条指令可以计算1×2的复矩阵乘与2×2的复矩阵的结果。当DSP用于多天线基站的基带处理时,有大量的矩阵乘法运算。这条指令能极大的降低cycle数。不过这条指令要使用多达10个寄存器,会导致严重的Register Pressure,影响循环的性能。所以要慎用。

ü  更宽的SIMD操作。C64+上大量的SIMD操作是2路的,C66上大量的SIMD操作是4路的。例如DSHR2, DSADD2等

ü  C66的MAC(乘累加)能力有了3倍以上的提升,

 

局限

ü  C66的寄存器个数和C64+一样,只有64个。

ü  C66的D单元的load/store宽度和C64+一样只有64bit。

 

下表列出了这个文档的学习重点。

章节

pages

导读

2 Overview of the TMS320C6600

 

 

2.1 Key Additions

 

 

2.2 C66x Floating-Point Overview

 

 

2.3 128-Bit Data Type

 

 

3 C66x Floating-Point and Vector/Matrix Operations and Optimizations

 

 

3.1 Floating-Point Arithmetic

15

 介绍了用浮点的复乘和求倒指令优化循环的例子。优化后的代码cycle数减小,计算精度提升

3.2 Complex Matrix Operation and Vector Operations using Advanced C66x Fixed-Point Instructions.

4

 介绍了用cmatmpyr1结合dsadd2指令计算4×4复矩阵乘与4×4复矩帧的代码优化。优化的结果是pipeline起来计算这样一组乘法需要12个cycle

3.3 Matrix Inversion Considerations .

1

 简单描述了C66x用于矩阵求逆运算的优势,没有例子

4 Additional Tuning Techniques for C66x Software-Pipelined Loops .

 

 

4.1 Reducing Register Pressure in the TMS320C66x .

14

 结合MMSE equalizer的例子介绍了怎样解决寄存器压力过大的问题。C66的寄存器个数和C64+相同,在使用了占用大量寄存器的指令(例如矩阵乘和更宽的SIMD)以后很容易出现寄存器压力过大的问题。所谓寄存器压力过大就是由于循环中有大量临时变量。编译器没有足够的寄存器分配给这些临时变量,编译器不得不减少展开的次数或者把部分局部变量存在stack中,这都会导致循环效率的降低。减少寄存器压力过大的主要方法是掺杂使用4way SIMD指令和2 way SIMD指令。不过这又会影响A B datapath的balance,所以要找到最好的平衡点需要不断尝试。这里还介绍了使用DMV指令消除寄存器压力过大的方法。不过这种用法太讲究。需要对pipeline做细致的分析。只有精益求精抠一两个cycle的时候才会用到。

4.2 C66x Limitation on Common Sub Expressions (CSE) .

2

 在C66上如果指令的计算结果是128bit数,而且这个结果会被多出使用,那么一定要定义一个128bit临时变量,先把计算结果赋值给这个临时变量,再在其他多处使用这个临时变量。

这是由于

Limitation on Common Sub Expressions

需要check一下这在7.4的编译器上是不是已经被fix了。

4.3 Live-Too-Long Problem in C6000 C Code.

2

 解释什么是临时变量live-too-long的问题。因为编译器生成的pipeline信息中有时会报register live too long ,这里形象的解释了这个问题。

 

1.3    Hand-Tuning Loops and Control Code on the TMS320C6000

 

文档下载链接 http://www.ti.com/lit/an/spra666/spra666.pdf

Hand-Tuning Loops and Control Code on the TMS320C6000 (spra666)

这个文档1~4章的内容在TMS320C6000 Programmer’s Guide中都有介绍,第五章可以着重看看。介绍了Optimizing Control Code的方法。所谓的控制代码就是循环比较少,没有密集运算的代码。或者是有循环,但是循环内部有大量结构体域的操作或者有大量判断的代码。这样的代码通常因为dependency的原因编译器不能优化的很好。需要人工做些代码调整。

章节

pages

导读

5.1 Restrict Qualifying Pointers Embedded in Structures

6

 在结构体中restrict-qualification是不能传递的。假设有结构体

typedef struct{

   short * data1;

   short * data2;

}s;

即使我们声明s结构体指针为restrict-qualified,但是结构体中的指针s->data1, s->data2p仍然是non-qualified。

解决的办法是代码中定义局部的restrict-qualified指针。把结构体内部的指针赋值给这些局部指针,用局部指针来访问数组

例如: 下面定义了局部指针ptr1和ptr2。把结构体内部指针s->data1和s->data2赋值给它们。后续的运算使用指针ptr1和ptr2来访问数据。

short * restrict ptr1;

short * restrict ptr2;

ptr1 = s->data1;

ptr2 = s->data2;

5.2 Optimizing “If” Statements

 

 在循环中如果有复杂的if/else分支会导致Disqualified loop,编译器不能产生software pipeline。这一张通过7个小节case by case的介绍了怎样拆解简化循环中的条件分支:

5.2.1 If-Conversion

5.2.2 “If” Statement Reduction When No “Else” Block Exists

5.2.3  “If” Statement Elimination

5.2.4 “If” Statement Elimination By Use of Intrinsics

5.2.5 “If” Statement Reduction Via Common Code Consolidation

5.2.6 Eliminating Nested “If” Statements

[Comments]1~6小节主要是编程技巧

5.2.7 Optimizing Conditional Expressions

[comments]多个条件相与相或要使用&和|,而不要使用&&和||,&&和||其实等效于嵌套循环。

5.3 Handling Function Calls

 

 介绍了编译器自动inline的原则以及编译选项-oi的参数对自动inline程度的影响

5.4 Improving Performance of Large Control Code Loops

 

包括两个小节:

 5.4.1 Using Scalar Expansion to Split Loops

太大太复杂的循环会导致" Disqualified loop: Too many instructions ",也就是由于循环中的运算操作太多,导致编译器放弃做software pipeline优化。解决这个问题的办法是把大循环劈开到成多个小循环。

5.4.2 Optimizing Sparse Loops

所谓稀疏循环是指循环中有条件判断,当条件成立才执行大量运算,而条件成立的概率不大。优化这种循环的办法是把原来的大循环拆成两个小循环,第一个循环先计算并存储条件成立的index,第二个循环在按照这个index列表对满足条件的数据机型处理。

 

1.4    TMS320C6000 Optimizing Compiler User's Guide

文档下载链接 http://www.ti.com/lit/ug/spru187u/spru187u.pdf

TMS320C6000 Optimizing Compiler User's Guide (spru187u)可以作为参考文档,下图

红框标出了对初学者比较有用的章节

TI C66x Optimization startup guide.pdf