CPE:每个元素的周期数
例如4Hz处理器表示时钟运行频率为每秒4*10^9个周期,以元素为x轴,周期为y轴,斜率为CPE。所以程序的运行时间主要由CPE决定。(图CP346:求数组元素之和)
妨碍优化的因素:程序中严重依赖于执行环境的方面。
void f1(int *p1,int *p2)
{
*p1+=*p2;
*p1+=*p2;
}
//由于编译器只实行安全的优化,由于编译器无法得知f1被调用时是否存在内存别名使用(2个指针指向同一位置),编译器无法优化成下面的形式。
void f1(int *p1,int *p2)
{
*p1+=2* *p2;
}
- 高级设计
选择适当的算法和数据结构 - 基本编码原则
减少函数调用
代码移动
#define INDENT …//初值
#define OP …
void f1(vec_ptr v,data_t *dest)
{
int i;
*dest = INDENT;
for(i=0;i<vec_length(v);i++){
data_t val;
get_vec_element(v,i,&val);
*dest=*dest OP val;
}
}
/*由于编译器无法判断函数的副作用,需要显式的移动代码vec_length(v)*/
void f2(vec_ptr v,data_t *dest)
{
int i;
int length=vec_length(v);
*dest = INDENT;
for(i=0;i<length;i++){
data_t val;
get_vec_element(v,i,&val);
*dest=*dest OP val;
}
}
void f3(vec_ptr v,data_t *dest)
{
int i;
int length=vec_length(v);
*dest = INDENT;
data_t *data=get_vec_start(v);
for(i=0;i<length;i++){
*dest=*dest OP *data[i];
//取消调用get_vec_element
}
}
- 消除不必要的内存引用,使用临时变量保持中间结果。
由于*dest的地址保存在寄存器%rbx,编译器在循环中需要从内存中读取2次后再写入。所以引入临时变量acc记录累积值,这样只需要1次读。
void f4(vec_ptr v,data_t *dest)
{
int i;
int length=vec_length(v);
data_t acc = INDENT;
data_t *data=get_vec_start(v);
for(i=0;i<length;++i){
acc OP= *data[i];
//取消调用get_vec_element
}
}
- 低级优化
通过检测f4的CPE(表CP362)发现函数的性能主要取决于OP。
循环寄存器:位于该寄存器的值会在迭代中传递
观察f4的数据流图(CP364b),只关注针对循环寄存器的操作,即OP(关键路径)和++i.
- 展开循环(K×1)
增加每次迭代计算的元素数量,减少循环次数
减少非循环数据相关操作的次数
void f5(vec_ptr v,data_t *dest)
{
int i;
int length=vec_length(v);
//首先需要保证第1次循环不会超出数组的界限
int limit=length-1;
data_t acc = INDENT;
data_t *data=get_vec_start(v);
for(i=0;i<limit;i+=2)
acc = (acc OP data[i]) OP data[i+1];
for(;i<length;++i)
acc OP= *data[i];
}
通过对比f5(CP369)f4的数据流图,关键路径的长度并未减少。
- 提高指令级并行
//重新组合变换CP375
acc = acc OP (data[i] OP data[i+1]);
//此时计算data[i] OP data[i+1]无需知道上一个acc的值
void f6(vec_ptr v,data_t *dest)
{
int i;
int length=vec_length(v);
//首先需要保证第1次循环不会超出数组的界限
int limit=length-1;
data_t *data=get_vec_start(v);
//使用多个累积变量
data_t acc1 = INDENT;
data_t acc2 = INDENT;
//K×K循环展开:将循环展开k次,并行积累k个值
//减少整体等待操作完成的时间
for(i=0;i<limit;i+=2)
acc1 = acc OP data[i];
acc2 = acc OP data[i];
for(;i<length;++i)
acc OP= *data[i];
}
- 功能性风格的条件操作
用条件操作来计算值,用于更新程序状态
int max=a[i]<b[i]?a[i]:b[i];//使用条件传送
示例:CP390
Amdahl定律:
对系统某一部分加速时,其效果取决于该部分的重要程度和加速效果。
局部性原理:CP418
- 时间局部性:
被引用过的内存位置可能在不久被多次引用 - 空间局部性:
可能会引用其附近的内存位置
例如对二维数组进行遍历时,由于数组按照行优先的顺序进行存储,先遍历同一行的元素;对于数组元素的引用的步长越短。
以CP448的矩阵相乘为例,前4个版本都在迭代中使用列变换,导致更多次数的缓存不命中,降低CPU的读写效率。