精通安卓性能优化-第一章(二)

优化Fibonacci

我们要做的第一个优化是消除一个方法调用,如Listing1-3所示。因为这个实现是递归的,在方法中移除一个函数调用会显著的减少总的方法调用次数。比如,computeRecursively(30)产生2692537次调用,而computeRecursivelyWithLoop(30)仅产生1346269次。然而,根据上面定义的标准,100毫秒或者更少的时间,这个方法的性能还是不可以接受,computeRecursivelyWithLoop(30)需要大约270毫秒。

Listing 1-3 优化Fibonacci序列的递归实现

public class Fibonacci {
    public static long computeRecursivelyWithLoop(int n) {
        if(n>1) {
            long result = 1;
            do {
                result += computeRecursivelyWithLoop(n-2);
                n--;
            } while(n>1);
            return result;
        }
        return n;
    }
}


NOTE:这不是一个真正的尾递归优化。

从递归到迭代

第二个优化,我们将递归实现切换成迭代实现。递归算法对开发者来说声誉很差,特别是在内存不多的嵌入式系统中,因为它们会消耗很多的栈空间,还有像我们刚刚看到的,太多的方法调用。即使性能是可以接受的,一个递归算法可能会引起栈溢出,造成程序crash。因此会尽可能选择迭代算法。Listing 1-4给出了Fibonacci序列的一个教科书式的迭代算法实现。

Listing 1-4 Fibonacci序列的递归实现

public class Fibonacci {
    public static long computeIteratively(int n) {
        if(n>1) {
            long a = 0, b = 1;
            do {
                long tmp = b;
                b += a;
                a = tmp;
            } while (--n > 1);
            return b;
        }
        return n;
    }
}

因为第N个Fibonacci序列的值是前面两个值的简单加和,一个简单的循环就可以做到。和递归算法相比,迭代算法的复杂度同样大幅降低,因为它是线性的。结果,它的性能同样更好,computeIteratively(30)需要少于1毫秒计算完成。因为它的线性特性,你可以使用这样一个算法去计算序列第30个以外的值。比如,computeIteratively(50000)需要大约2毫秒,通过线性投影推算,你可以猜测computeIteratively(500000)将需要20到30毫秒。

尽管这样的性能已经远远超越了可接受的目标(100ms或者更少),对这个算法做一点修改可以得到更快的结果,如Listing 1-5所示。这个新的版本每次循环计算两个值,总的迭代次数减少到一半。由于在最初的迭代算法中迭代次数可能是奇数,对a和b的初始值做相应的修改:当n是奇数的时候,序列从a=0和b=1开始;当n是偶数的时候,序列从a=1和b=1(Fib(2)=1)开始。

Listing 1-5 修改过的Fibonacci序列的迭代实现

public class Fibonacci {
    public static long computeIterativelyFaster(int n) {
        if (n>1) {
            long a, b = 1;
            n--;
            a = n & 1;
            n /= 2;
            while (n-- > 0) {
                a += b;
                b += a;
            }
            return b;
        }
        return n;
    }
}

结果显示这个修改后的算法运行速度大概是原算法的2倍。

尽管迭代的实现方式已经很快,它有一个主要的问题:可能不会返回正确的结果。问题在于返回的结果通过64位的long保存。64位可以容纳的最大的Fibonacci数值是7,540,113,804,746,346,429,第92个Fibonacci值。尽管当传入值大于92的时候,这个方法会返回而不会发生程序crash,但是因为溢出会返回一个不正确的值:第93个Fibonacci值将会是一个负值!递归实现有同样的限制,不过用户要非常有耐心才会发现。

NOTE:Java指定了所有原子类型的大小(除了boolean型):long, 64位;int, 32位;short, 16位。所有的整型数值是有符号数。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值