性能小贴士

性能小贴士

本文主要介绍一些代码优化方面的小贴士,结合起来使用能总体性的提高应用性能。可是,这些技巧不可能带来戏剧性的性能改变。合适的算法和数据结构是解决性能的首选考虑(还有程序的执行流程优化),但这已经脱离了本文的范畴。java

本文介绍的小贴士是每一个有追求的程序员应有的编码习惯。程序员

关于如何写出高效的代码,这里有两个基本的原则:算法

  • Don't do work that you don't need to do编程

  • Don't allocate memory if you can avoid it数组

面临的现状

一个很是棘手的问题,你的应用运行在不一样类型的硬件设备上的。不一样版本的虚拟机以不一样的运行速度运行在不一样的处理器上。一般,不能轻易的下结论说"设备x比设备y快或者慢了多少倍"。尤为是,模拟器和真机的差异很是大,模拟器上的性能测试不能充分反应真机的状况,何况,一个有JIT和一个没有JIT的设备之间也存在巨大的差异。数据结构

为了保证你的应用性能在绝大多数的设备上有一个好的表现,确保你的代码在各个方面的高效性并不遗余力去优化你的性能。架构

避免建立没必要要的对象

对象的建立毫不是免费的。如今主流的垃圾收集器中每一个线程的临时对象分配池使得对象的建立更加低廉,可是分配内存的代价仍是要比不分配要高昂的多。 正如你在程序里建立了大量的对象,垃圾回收会按期的触发,会形成一点点卡顿的感受。虽然Android在2.3引入了并发的垃圾回收机制,但没必要要的工做仍是应该避免。并发

所以,不要建立那些非必需的实例对象。如下的几个例子可能具备参考意义:函数

  • 若是你有一个方法返回一个string,并且你知道这个返回结果是要被加到一个StringBuffer中去的,那么改变你的方法实现,变成直接添加而不是建立一个短暂的临时对象。oop

  • 当从一个输入数据中提取字符串,尝试去返回原数据的子串,而不是建立原数据的一个拷贝。采用子串虽然也会建立一个新的string对象,可是数据的内容仍是和原数据共用的。

  • 尽量的使用一维数组,而不是多维的数组。

关于减小没必要要的对象建立,咱们还能够有如下的实践:

  • 使用Message.obtain()或者handler.ontainMessage()去获取一个Message对象,而不是直接new。

  • 使用优化过的数据结构,SparseArray来替代Map。

  • 使用线程池去重用线程而不是每次新建线程。

  • Handler随意建立,只为把内容放到主线程执行,这个时候能够公用一个Handler。

  • 在那些被高频调用的方法里,避免建立临时对象,好比View的onDraw(),在循环里等等。

  • 在API23如下,当作图片清理设置ImageView.setImageBitmap(null)时,方法内部会建立一个BitmapDrawable对象。因此,咱们 使用ImageView.setImageDrawable(null)来代替。

总的来讲,尽你所能的避免建立短暂的临时对象。垃圾回收对体验有较大的影响,越少的对象建立意味着越低频的垃圾回收。

Prefer Static Over Virtual

把一个方法定义成static的,相比于对象方法大概会有15~20%的速度提高。这也是一个好的实践,由于你能够从方法声明上分辨出这个方法调用不会改动对象的状态。

使用Static Final声明常量

如下是一个类在顶部的声明:

static int intVal = 42;
static String strVal = "Hello, world!";

编译器会生成一个类的初始化方法,叫作 <clinit>, 会在此类第一次被引用时执行。这个方法会将42存储到intVal,而且会为strVal从类文件的字符串常量池中提取一个引用。当这些值在稍后被引用到,它们经过字段查找被访问到。

咱们能够经过使用“final”关键字来提高:

static final int intVal = 42;
static final String strVal = "Hello, world!";

如此一来,这个类的初始化<clinit>方法就不会被调用,由于这两个常量直接存放进dex文件的静态字段初始化器中。指向intVal的引用直接使用42,访问strVal也会使用一个相对廉价的字符串常量而不是经过字段查找的方式。

注意:这个优化只适用于基本类型以及字符串常量,而非任意的引用类型。可是,为常量加上static final的声明是一个好习惯。

避免内部的Getters/Setters

在像C++这样的本地语言中,经过getters方法的形式替代直接的字段访问会比较日常。这在C++里是一个优秀的习惯,在C#或者Java这样的面向对象的语言里也是被常用,由于大部分的编译器都作了内联优化。

然而,这在Android上并非一个好的实践。方法的调用比实例字段的查找要昂贵的多。遵循面向对象的编程习惯提供setter和getter方法是好的,可是在类的内部应该直接使用字段访问。

在没有JIT的状况下,直接的字段访问要比调用一个无用的getter方法快大约3倍。在有JIT(直接的字段方法跟本地访问同样低廉)的状况下,直接的字段访问要比方法调用快大约7倍。(特意作了验证,在我6.0系统的motox设备上大约是10倍)

使用加强的for循环

加强的for循环也叫"for-each"循环,一般用于遍历那些实现了Iterable接口的集合。在集合中,一个迭代器通常会调用hasNext()next()。在使用ArrayList时,一个手写的计数的循环在有JIT的状况下性能大约是没有时的3倍,可是在使用其它的集合时使用加强的for循环的性能跟显示的调用迭代器的性能基本接近。

下面是迭代一个数组的几种可选方案:

static class Foo {
    int mSplat;
}

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
         sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

public void two() {
    int sum = 0;
    for (Foo a : mArray) {
        sum += a.mSplat;
     }
}

zero()是最慢的,由于JIT不能优化掉每次循环中的数组长度的查找。

one()要快一点。它把全部的都放到本地变量,避免了查找。但其实这里只有数组长度的定义能提高性能。

two()在没有JIT的状况下是最快的,在有JIT的状况下跟one()基本同样。

因此,默认状况下咱们应该使用加强的for循环,但在处理ArrayList时,若是对性能特别敏感的状况下,咱们考虑使用手写的计数循环。(依然是motox手机,100万的数据量遍历,手写的带计数的循环的性能要比for loop快15倍左右)

对于私有的内部类,考虑使用包访问权限代替私有访问权限

以下的类定义:

public class Foo {
    private class Inner {
        void stuff() {
            Foo.this.doStuff(Foo.this.mValue);
        }
    }

    private int mValue;

    public void run() {
        Inner in = new Inner();
        mValue = 27;
        in.stuff();
    }

    private void doStuff(int value) {
        System.out.println("Value is " + value);
    }
}

上述代码中,咱们定义了一个私有的内部类(Foo$Inner),这个私有类直接访问了外部类的一个私有方法doStuff(), 同时访问了外部对象的私有的字段。这是合法的,代码也如预期打印出了"Value is 27"。 问题在于虚拟机认为直接从Foo$Inner访问Foo的私有方法是不合法的,由于这两个是不一样的类,然而Java的语法又容许这样的访问。为了解决这个问题,编译器生成了一系列的合成方法:

/*package*/ static int Foo.access$100(Foo foo) {
    return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
    foo.doStuff(value);
}

当内部类代码须要访问外部类中的mValue字段或者调用doStuff()方法时会调用这些静态方法。这就意味着上述的代码归根结底到了这样一种场景,经过方法的存取来访问字段。在前面有讲到过,存取要比直接的字段方法要慢。因此,这个例子展现了一个特定的语言特性所引发的无形的性能损耗。

多使用系统提供的函数

除了常规的理由外,咱们更愿意选择系统函数的缘由是由于系统可能会使用汇编的方式去替换方法的实现,这比JIT生成的代码有着更好的性能。典型的有String.indexOf()和相关的APIS。相似的,在有JIT的Nexus One手机上,System.arraycopy()方法要比你手写的循环快9倍左右。

慎用本地方法

使用NDK经过C++等本地代码开发出来的应用的性能并不必定比用Java代码来的高效。至少,Java-Native是有传输消耗的,JIT也没法跨越这个边界进行优化。并且你还会面临如下的问题:

  • 若是你分配了本地的资源,就须要考虑如何及时的回收这些资源

  • 你须要为要支持的CPU架构编译不一样的动态库

  • 就算是相同的CPU架构,你可能须要编译不一样的版本。举个例子,为G1手机的ARM架构的处理器编译的动态库在Nexus One上不能发挥出应有的效果,而为Nexus One编译的动态库,在G1上根本就跑不起来。

本地代码最主要的使用场景是复用已有C/C++代码把它们使用到Android里来,而不是为了提高速度。

多测量

开始优化前,首先要确认问题是否存在。其次,你要能精确的测量出当前问题的性能数据,不然就不能很好的量化提高效果。(对于性能问题都须要用数听说话)

总结

今天讲的内容有什么用,能减少多少内存,提高多少程序性能呢?

做为一个有追求的程序员,本文的小技巧都应该归入我的的编码习惯。

如今的设备硬件性能这么强了,还有必要作这些细节优化吗?

虽然,如今的设备的硬件性能很是强了,可是应用程序面临的内存、性能等问题仍然很是严峻。好比说,加载几张高清图,
不作任何限制就能让程序崩溃。
    又好比,上面提到过的语言层面的一些性能损耗。面向对象的java语言在建立对象时,会先建立它   所继承的父类的对象。
建立一个继承关系比较复杂的类的实例时,会顺带建立不少父类实例。这就是无形的性能损耗。然而面向对象能带来的好处让
咱们只能选择妥协。因此,相比于这些咱们没法解决或者很难解决的问题,本文所介绍的技巧能够算是性价比很是高的实践。
相关文章
相关标签/搜索