Data: 2017.12.28
Author: cjh
Theme: Memory Dependencies
本篇内容是基于TI TMS320C6000Programmer’s Guide手册第2.2.2 Memory Dependencies章节写的,主要是有关内存依赖和restrict关键字的使用,基于原文翻译和自己的理解,有错的地方还请指正。
为了尽可能的提高代码的效率,C6000编译器可以并行调度尽可能多的指令。要并行安排指令,编译器必须确定指令之间的关系或相关性。Dependency意味着一条指令必须依赖另一条指令发生。例如,变量必须先从内存中加载,然后才能使用。因为只有独立的指令可以并行执行,所以依赖性会抑制并行性。
■ 如果编译器无法确定两条指令是独立的(例如,b不依赖于a),则它假定依赖关系,并按顺序调度这两条指令,以计算完成第一条指令所需的任何等待时间。
■ 如果编译器可以确定两条指令是相互独立的,它可以并行调度它们。
编译器通常很难确定访问内存的指令是否独立。 以下技术可帮助编译器确定哪些指令是独立的:
① 使用restrict关键字来指示指针是唯一可以指向声明该指针的作用域中的特定对象的指针。
② 使用-pm(程序级优化)选项,它使编译器可以全局访问整个程序或模块,并允许它在排除依赖性方面更加积极。
③ 使用-mt选项,它允许编译器使用允许消除依赖关系的假设。 请记住,在线性汇编代码上使用-mt选项相当于将.no_mdep指令添加到线性汇编源文件。应使用.mdep指令指定特定的内存依赖性。 有关程序集优化程序的更多信息,请参阅TMS320C6000 Optimizing Compiler User’s Guide。
为了更好的解释memory dependencies,见下图程序和流程
图中的1和5表示的是完成该指令所需要的周期数。Dependency所指的是:
■ 从sum [i]返回到in1 [i]和in2 [i]的路径时,写入sum可能会影响in1或in2所指向的内存。
■ 从in1或in2中读取数据只能等到完成相加后才会执行,这会产生别名问题。当两个指针指向相同的内存位置时,会发生混叠。例如,如果vecsum()在具有以下语句的程序中被调用,则in1和sum别名,因为它们都指向相同的内存位置:
short a[10], b[10];
vecsum(a, a, b, 10);
Restrict关键字
为了帮助编译器确定内存依赖性,可以使用restrict关键字修饰指针,引用或数组。他能保证在指针声明的范围内,指向的对象只能被该指针访问。任何违反此保证都会使程序变得不稳定。这种做法有助于编译器优化代码的某些部分,因为可以更容易地确定混叠信息。
举个例子:
int ar[10];
int * restrict restar = (int *) malloc(10 *sizeof(int));
int * par = ar;
这里,指针restar是访问由malloc()分配的内存的惟一且初始的方式。因此,它可以由关键字restrict限定。然而,par指针既不是初始的,也不是访问数组ar中数据的惟一方式,因此不可以把它限定为restrict。
现在考虑下面这个更复杂的例子,其中n是一个int:
for(n=0; n < 10; n++)
{
par[n] += 5;
restart[n] += 5;
ar[n] *= 2;
par[n] += 3;
restar[n] += 3;
}
知道了restar是访问它所指向数据块的惟一初始方式,编译器就可以用具有同样效果的一条语句来代替包含restar的两个语句:
restar[n] += 8;
然而,将两个包含par的语句精简为一个语句将导致计算错误:
par[n] += 8;
出现错误结果的原因是循环在par两次访问同一个数据之间,使用ar改变了该数据的值。
没有关键字restrict,编译器将不得不设想比较糟的那种情形,也就是两次使用指针之间,其他标识符可能改变了数据的值。使用restrict关键字之后,编译器可以放心地寻找计算的捷径。
可以将关键字restrict作为指针型函数参量的限定词使用。这意味着编译器可以假定在函数体内没有其他标识符修改指针指向的数据,因而可以试着优化代码,反之则不然。
关键字restrict有两个读者。一个是编译器,它告诉编译器可以自由地做一些有关优化的假定。另一个读者是用户,它告诉用户仅使用满足restrict要求的参数。一般,编译器无法检查您是否遵循了这一限制,如果您蔑视它也就是在让自己冒险。
参考: