zoukankan      html  css  js  c++  java
  • 《深入理解计算机系统》 优化程序性能的几个方法

    本文几个优化程序性能的方法出自CSAPP第五章,通过不断修改源代码,试图欺骗编译器产生有效的代码

    我们先引入度量标准每元素的周期数(CPE),表示程序性能。

    我们先定义一个数据结构   data_t 代表数据类型

    1 typedef struct{
    2   long len;
    3   data_t *data;        
    4 }vec_rec,*vec_prt;

    以及常数IDENT和OP以便在后续的代码中进行不同的操作

    //对所有向量的元素求和
    #define IDENT 0
    #define OP +
    
    //对所有向量元素乘积
    #define IDENT 1
    #define OP *

    我们首先看最初的代码版本,这是一个具有很大优化空间的代码,具体函数实现可参考原书。

     1 void combine1(vec_ptr v, data_t *dest)
     2 {
     3     long int i;
     4     *dest = IDENT;
     5     for (i = 0; i < vec_length(v); i++) {  //vec_length返回向量长度
     6         data_t val;
     7         get_vec_element(v, i, &val);//先进行边界检查再获取索引 i 处的值并赋值给val
     8         *dest = *dest OP val;
     9     }
    10 }

    1.消除循环的低效率

    因为每次迭代循环的时候都必须对测试条件求值,但在此循环中,向量的长度值并不会随着循环的进行而改变,因此只需要计算一次vec_length(v)并保存在一个变量中,在后续的循环中使用此变量。

    因此我们得到第二个版本的代码。这一常见的优化方式称为 代码移动,即识别要执行多次但值不会改变的代码,将其移动到代码前部分,避免重复求值。

     1 void combine2(vec_ptr v, data_t *dest)
     2 {
     3     long int i;
     4     long int length = vec_length(v);//只进行一次计算
     5     *dest = IDENT;
     6     for (i = 0; i < length; i++) {
     7         data_t val;
     8         get_vec_element(v, i, &val);
     9         *dest = *dest OP val;
    10     }
    11 }

    2.减少过程调用

    过程调用(函数调用)会带来开销,因此我们增加一个函数 get_vec_start.

    1 data_t *get_vec_start(vec_ptr v)
    2 {
    3   return v->data;  
    4 }

    由此我们可得第三版代码

    void combine3(vec_ptr v,data_t *dest)
    {
      long i;
      long length = vec_length(v);
      data_t *data = get_vec_start(v); 
    
      *dest = IDENT;
      for(i = 0;i<length;i++){
          *dest = *dest OP data[i];  //在循环中减少过程调用
      }
    }

    3.消除不必要的内存引用

    虽然我们在第三版的代码中减少了过程的调用,但是第三版的代码相比第二版代码性能并没有明显的提升,这说明第三版中的代码还有别的制约性能的因素。

    先看第三版代码的内循环汇编代码:

    //dest in %rbx, data+i in %rdx, data+length in %rax
    
    .L17
        vmovsd (%rbx),%xmm0
        vmulsd (%rdx),%xmm0,%xmm0
        vmovsd %xmm0,(%rbx)
        addq $8,%rbx
        cmpq %rax,%rdx
        jne    .L17

    由汇编代码可见,第三版的代码对内存进行了两次读操作,一次写操作,通过引入一个临时变量,使其在循环中累计值,在循环结束后再讲值写入内存。

    这样我们将循环中的内存操作又两次读一次写减少到一次读操作。程序性能显著提高。

    void combine4(vec_ptr v, data_t *dest)
    {
        long int i;
        long int length = vec_length(v);
        data_t *data = get_vec_start(v);
        data_t acc = IDENT;
        for (i = 0; i < length; i++) {
            acc = acc OP data[i];
        }
        *dest = acc;
    }
  • 相关阅读:
    信用评分卡Credit Scorecards (1-7)
    数据可视化 – 银行案例学习实例 (Part 1-6)
    CatBoost算法和GPU测试(python代码实现)
    xgboost调参指南
    Dream team: Stacking for combining classifiers梦之队:组合分类器
    集成学习算法汇总----Boosting和Bagging(推荐AAA)
    算法优点和缺点汇总(推荐AAA)
    (剑指Offer)面试题59:对称的二叉树
    (笔试题)质数因子Prime Factor
    (笔试题)把一个整数数组中重复的数字去掉
  • 原文地址:https://www.cnblogs.com/blzm742624643/p/9687690.html
Copyright © 2011-2022 走看看