本篇博客主要谈论C语言关键字restrict,只用于修饰指针,功能是帮助编译器进行优化!值得注意的是,优化需要就事论事,经过分析:如果发现确实优化不了,就不优化了。不加restrict关键字修饰的指针,编译器一定不进行优化!
目录
2.2 Target Code Optimizer(目标代码优化器)
一、 定义
1.1 概念
关键字restrict只用于限定指针,表明本指针是访问一个数据对象的惟一且初始的方式。
1.2 目的
关键字的用意是充分发挥多处理器(CPU只有1个)的并行性。并行的含义就和下面这句古文的含义很类似。注意和并发作区别。
并发是基于时间片轮转的,故而从宏观上看,各个进(线)程是一起运行的,微观上是串行的。并行就像是多向车道一样(车道数目------核数),各个进(线)程运行互不干扰。
屠惧,投以骨。一狼得骨止,一狼仍从。复投之,后狼止而前狼又至。骨已尽矣,而两狼之并驱如故。
------狼三则·其二(蒲松龄)
二、 实例
实例不胜枚举,在这里我举用两个例子。
2.1 利用并行拆分循环
void add(const double *x, const double *y, double *res)
{
int i;
for(i = 0; i < 6; ++i)
res[i]= x[i] + y[i];
}
情形一二的前提:假设数组x和数组y内存无重叠,但是数组x和数组res内存有重叠:
情形一、假设如图1 所示:res[i]依赖于res[i-1]的值,无法优化。
情形二、假设如图2 所示:res[5]依赖于res[0]的值,没有办法去做优化。
情形三的前提:假设数组x,y,res内存均有重叠:
情形三:假设如图3 所示,res[0...3]的计算没有依赖性,res[4...5]存在依赖性,无法优化。
总结:内存重叠存在的话,是无法进行优化的,因为不存在一劳永逸的办法解决这个问题。
最理想的情形:
假设x,y,res三者指向的内存空间不会重叠, 如果这个函数将在多处理器的环境下执行,编译器可能会做这样的优化:把一个循环拆成两个循环,一个处理器计算res[0...2],另一个处理器计算res[3...5],两个处理器可以同时工作,使计算时间理论上缩短一半,同时保证了结果的正确性。如果程序员已经能够保证内存不重叠的话,就有必要指定restrict允许编译器进行优化。
2.2 Target Code Optimizer(目标代码优化器)
--------参考文献 余甲子,石凡,潘爱民.程序员自我修养[M].北京:电子工业出版社,2009.4:45-47.
说明:例子为源代码是不合适的,因为目标代码优化器操作的对象是目标代码(x86的汇编语言),但是可以帮助我们理解使用关键字restrict进行优化的过程。
int ar[10];
int* par = ar; //既可以通过par也可以通过ar访问本数组
restrict int* p1 = (int*)malloc(10 * sizeof(int));
for(int i = 0; i < 10; ++i)
{
par[i] += 5; //语句1
p1[i] += 5; //语句2
arr[i] *= 2; //语句3
par[i] += 3; //语句4
p1[i] += 3; //语句5
}
p1存在restrict修饰,含义就是p1所指向的空间只会由p1去修改,故而语句2和语句5可以合并为一句。ar或者par都不能指定为restrict,二者深层次原因是不同的——ar不能指定是唯一性不满足,par不能指定是唯一性和初始性同时不满足(何为唯一性、初始性看本博客1.1节)。
优化后的结果可能是这样的,语句5和并入语句2;修改语句2,删除语句5:
int ar[10];
int* par = ar; //既可以通过par也可以通过ar访问本数组
restrict int* p1 = (int*)malloc(10 * sizeof(int));
for(int i = 0; i < 10; ++i)
{
par[i] += 5; //语句1
p1[i] += 8; //语句2
arr[i] *= 2; //语句3
par[i] += 3; //语句4
}