关于代码编写过程中的性能优化的理解

示例程序
private long mSum;

long combine1(long[] v){
    long i;
    
    for(i = 0; i < getLength(v) ; i++){
        long val = getElement(v,i);
        mSum = mSum + val;
    }
    
    return mSum;
}

int getLenght(long[] v){
    return v.length;
}

int getElement(long[] v,long index){
    if(index < 0 || index >= v.length){
        return 0;
    }
    
    return v[index];
}
优化一:消除循环的低效率

在示例的程序中,每次循环都需要测试i < getLength(v)这个条件,而这个值是固定的,没有必要每次判断该条件的时候都计算一次该值。

优化二:减少过程调用

在示例程序中,循环中每次都要调用getElement()方法,但这个方法可以不用,将方法中的代码写到循环体中,从而减少过程的调用所带来的开销,此外在getElement()方法中都在进行边界的检测,而在当前的循环调用中每次的角标i都是合法的,减少过程调用也可以避免边界检测所带来的开销。

优化三:消除不必要的内存引用

在示例程序中,每次的循环操作都需要调用mSum的值,每次调用mSum都需要从堆内存中读取和写入该值,可以使用局部变量来代替mSum,只需将最后得到的结果赋值给mSum即可,这种优化能够大大的提高程序的性能。

简单优化后的示例程序
private long mSum;

void combine2(long[] v){
    long i;
    int len = v.length;
    long sum = 0;
    
    for(i = 0; i < len ; i++){
        sum += v[i];
    }
    
    mSum = sum;
}
简单优化前后的性能对比
  • 整数加法:优化前的每元素的周期数(CPE)为22.68,优化后的为1.27
  • 整数乘法:优化前的CPE为20.02,优化后为3.01
  • 浮点加法:优化前的CPE为19.98,优化后为3.01
  • 浮点乘法:优化前的CPE为20.18,优化后为5.01
优化四:循环展开

循环展开是一种程序变换,通过增加每次迭代计算的元素的数量,减少循环的迭代次数。首先,他减少了不直接有助于程序结果的操作数量,例如循环索引计算和条件分支;第二,他提供了一些方法,可以进一步优化代码,减少整个计算中关键路径上的操作数量。示例代码如下(每次循环做两次的求和):

private long mSum;

void combine3(long[] v){
    int i;
    int len = v.length;
    int limit = len - 1;
    long sum = 0;
    
    for(i = 0; i < limit ; i+=2){
        sum = (sum + v[i]) + v[i+1];
    }
    
    //如果len为奇数,需再执行一次运算
    for(; i < len ;i++){
        sum += v[i];
    }
    
    mSum = sum;
}

此种优化方法主要针对整数的加法运算,对于整数乘法、浮点加法和浮点乘法的效果不大,因为在之前的优化中,只有整数加法的CPE未达到延迟界限,而其他三种运算均已到达,而这种优化方式无法打破延迟界限。

延迟、发射时间和容量
  • 延迟:表示完成运算搜需要的总时间
  • 发射时间:表示两个连续的同类型的运算之间徐涛的最小时钟周期数
  • 容量:表示能够执行该运算的功能单元的数量
  • 延迟界限:简单理解就是延迟,整数加法的延迟界限为1,整数乘法的为3,浮点加法为3,浮点乘法为5
  • 吞吐量界限:即吞吐量,指发射时间除以容量的值,整数加法为0.5,整数乘法为1,浮点加法为1,浮点乘法为0.5
优化五:提高并行性
方式一:多个累积变量

对于一个可结合和可变换的运算来说,可以通过将一组合并运算分割成两个或者更多的部分,并在最后合并结果来提高性能,示例代码如下:(分割成两个运算)

private long mSum;

void combine4(long[] v){
    int i;
    int len = v.length;
    int limit = len - 1;
    long sum1 = 0;
    long sum2 = 0;
    
    for(i = 0; i < limit ; i+=2){
        sum1 += v[i];
        sum2 += v[i+1];
    }
    
    //如果len为奇数,需再执行一次运算
    for(; i < len ;i++){
        sum1 += v[i];
    }
    
    mSum = sum1 + sum2;
}
方式二:重新结合变换

通过改变运算的顺序来对性能进行优化,以combine3()中的代码为例:

private long mSum;

void combine5(long[] v){
    int i;
    int len = v.length;
    int limit = len - 1;
    long sum = 0;
    
    for(i = 0; i < limit ; i+=2){
        sum = sum + (v[i] + v[i+1]);
    }
    
    //如果len为奇数,需再执行一次运算
    for(; i < len ;i++){
        sum += v[i];
    }
    
    mSum = sum;
}

不难发现,同combine3相比,唯一的区别在于执行sum的运算时先求出v[i] + v[i+1]的值,然后再将结果同sum运算。

并行性性能总结

通过提高并行性,即便是2×的优化,也能使代码性能在原来的基础上提高两倍左右的性能(除了整数加法,因为在这个过程中其他非循环的操作影响了性能),毫无疑问都打破了延迟界限所带来的限制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值