CSAPP-程序优化

代码移动:

如果一个表达式总是得到同样的结果,最好把它移动到循环外面,这样只需要计算一次。编译器有时候可以自动完成,比如说使用 -O1 优化。一个例子:

void set_row(double *a, double *b, long i, long n){
  long j;
  for (j = 0; j < n; j++){
   a[n*i + j] = b[j];
  }
}

这里n*i是重复被计算的,可以放到循环外面

long j;
int ni = n * i;
for (j = 0; j < n; j++){
   a[ni + j] = b[j];
}

消除不必要的内存引用:

for(int i = 0;i < n;i++)
{
   *p = *p OP data[i];
}

//new
int t = *p;
for()
{
   t = t OP data[i];
}
*p = t;

上面的过程中,每次循环要经历2次读1次写:
从p读取值 -> 读取data[i]进行计算 -> 写到p
但该进之后,每次循环只需要一次读

vmovesd (%rbx),%xmm();        //read from p
vmulsd (%rdx),%xmm(),%xmm();  //mul by data[]
vmovesd %xmm(),(%rbx);        //store

vmulsd (%rdx),%xmm(),%xmm();  //mul by data[]

处理条件分支

现代处理器普遍采用超标量设计,也就是基于流水线来进行指令的处理,也就是说,当执行当前指令时,接下来要执行的几条指令已经进入流水线的处理流程了。

对于顺序执行来说,不会有任何问题,但是对于条件分支来说,在跳转指令时可能会改变程序的走向,也就是说,之前载入的指令可能是无效的。这个时候就只能清空流水线,然后重新进行载入。为了减少清空流水线所带来的性能损失,处理器内部会采用称分支预测』的技术。

例:
当遇到if语句的时候,你事先完全不知道应该往哪边走。你可以暂停,等待直到之前的指令执行完成,然后比较结果,然后往正确的那个方向走。
但是现代计算机有很长的流水线,等待结果必定会浪费很多时间。于是有了分支预测,先假设分支方向,加入流水线,如果对了就可以继续执行下去。否则清空流水线再向正确的地方前进。

条件转移:

在有的情况下,书写合适的条件转移可以实现分支。

if(a > b)
{
   int t = a;
   a = b;
   b = a;
}

上面的代码会进行分支预测,改:

if(a > b)
{
   int tmax = a < b? b:a;
   int tmin = a > b? b:a;
   a = tmin;
   b = tmax;
}
// a < b ? b:a;可以等效于:
tmax = b;
tmp = a;
t = a < b;
if(!t) tmax=tmp;

循环展开:

通过增加每次迭代的计算量,减少循环次数。
1.减少了循环索引计数和条件分支
2.提供了进一步利用机器特性进行的优化的机会

int ans = 0;
for(int i = 0;i < n;i += 2)
{
   ans = ans OP data[i] OP data[i+1];
}

提高并行化:

1.多个累积变量
在加法和乘法功能的时候,功能单元是完全流水线化的。使其能更高效的执行

for(int i = 0;i < n;i+=2)
{
   ans0 = ans0 OP data[i];
   ans1 = ans1 OP data[i+1];
}

与上面相比关键路径上面的操作由 n->n/2。而且结果ans0 和 ans1不相关,流水线性能应该会提升。

2.重新结合变化

 ans = ans OP data[i] OP data[i+1];
 ans = ans OP (data[i] OP data[i+1]);

可能看着没什么区别,但是data[i]*data[i+1]的计算完全不用考虑上一次循环的结果,流水线能得到充分利用


参考资料:

不周山之读薄CSAPP:http://wdxtub.com/2016/04/16/thin-csapp-0/
浅谈分支预测、流水线和条件转移:http://www.cnblogs.com/yangecnu/p/4196026.html#undefined
《深入理解计算机系统 》

posted @ 2017-03-04 16:24  Przz  阅读(740)  评论(0编辑  收藏  举报