程序优化

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的读写效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值