代码改变世界

android性能小贴士 翻译

2013-07-04 22:45  在移动的AK  阅读(197)  评论(0编辑  收藏  举报

转自http://developer.android.com/training/articles/perf-tips.html

性能小贴士:

 

这篇文档主要一些微优化可以提升应用程序性能,但是这些改变不会明显的提升巨大的性能改善。选择正确的算法以及数据结构应该是你优先考虑的事情,但是在这篇文档的讨论范围之外。你应该把这些贴士作为一个基本的代码实践可以用进你的习惯,为了大多数的代码效率。

 

写效率代码有两个基本的规则:

不要做你不需要实现的功能

不要分配你可以避免的内存

 

性能优化与设备的关系,大多数情况,不同的模拟器很少分辨出不同的设备上的性能表现。但是,在设备有JIT与没有上还是有巨大的区别:对于JIT最好的代码未必是不含JIT设备上最好的。

 

避免创建不需要的对象

对象的创建不是免费的,一个有着线程分配池的对象垃圾回收器为了暂时性的对象可以使分配更加廉价,但是分配分配内存依然很昂贵。

 

如果你有一个方法返回string,然后你知道这个结果将会返回一个stringbuffer,改变你的实现,直接append,而不是创建一个短暂的对象

 

当从一组输入数据去抽取string,试着去返回源数据的substring,而不是创建一个copy,这样你将会创建一个新的string对象,但是它会与源数据分享char[]数组。

 

一个更加彻底的方式是切开多维数组变成平行的单维数组

一个int数组比integer对象数组更佳,但是概括化来说,两个平行int数组比一个(int,int)对象数组更加有效率。这也同时适用于其他基本类型的。

如果你需要实现一个容器去存储(Foo,Bar)元对象,记住,两个平行数组Foo[] 和 Bar[]会更加优于一个单数组(Foo,Bar)对象。(但是,当你设计一个api的时候,创建小的合并为了更好的API设计,但是在你自己的内部代码,应当越简单越好)

 

简单来说,避免创建瞬时对象。越少的对象创建意味着更少的垃圾回收,但是对于用户体验会有直接的影响。

 

使用静态对比虚拟

如果不需要访问对象的变量,把方法静态化。调用将会提高15%-20%。这是好的实践,因为你可以调用方法不会改变对象的状态。

给定量使用static final

 

static int intVal = 42;

staticString strVal ="Hello, world!";

 

编译器会生成类初始化方法,叫做clinit,当类被第一次使用的时候会执行,没有使用final时,方法把值42存进变量intVal,然后抽取一个引用从类文件的固定变量表,当这些值被引用了,他们会通过field被访问。

 

staticfinalint intVal =42;

staticfinalString strVal ="Hello, world!";

 

这个类不再需要clinit这个方法,因为不变量会进入静态field 初始化在dex 文件。代码引用intval将会直接使用integer 值42,然后访问strval将会使用一个相关的不昂贵的string 固变量 来替代 field查找。

这个优化仅仅对于基本类型和string 不变量,而不是强引用类型。但是仍然是很好的习惯static final。

 

避免使用内部getters/setters

在android,虚拟方法调用是昂贵的,超过实例变量的查找。

没有JIT的话,如果直接变量访问将会是使用getter的三倍,使用JIT,直接的变量访问将会是调用方法的7倍速度

 

使用foreach循环

 

在集合里面,一个手工写的计算循环是for each的三倍速度快。

以下三个循环

staticclassFoo{

    int mSplat;

}

 

Foo[] mArray = ...

 

publicvoid zero(){

    int sum = 0;

    for (int i = 0; i < mArray.length; ++i) {

        sum += mArray[i].mSplat;

    }

}

 

publicvoid one(){

    int sum = 0;

    Foo[] localArray = mArray;

    int len = localArray.length;

 

    for (int i = 0; i < len; ++i) {

        sum += localArray[i].mSplat;

    }

}

 

publicvoid two(){

    int sum = 0;

    for (Foo a : mArray) {

        sum += a.mSplat;

    }

zero是最慢的,因为JIT目前还不能优化每次获取数组长度在每次循环重

one是更快的,它把每一个东西抽取到本地变量,避免了查找,只有数组长度提供了性能的优势

two在没有JIT的情况下是最快的,在有JIT的情况下和one没有区别,使用了循环的提升在java1.5之后提供的方法

 

 

考虑使用包替代私有内部类私有访问

publicclassFoo{

    privateclassInner{

        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);

    }

}

访问私有变量是通过访问方法的,所以访问速度变慢。

 

使用内部类访问使用包访问权限,而不是私有访问权限。

 

避免使用浮点型

在android里面浮点型比整型慢两倍多

float和double是一样的

所以优先使用double

 

了解和使用包

使用库的代码,而不是自己去实现。系统会替换到库方法。例如string的indexof,dalvik会使用内部内联来替换。例如,使用system.arraycopy方法比手动写的循环快9倍在JIT的基础上。

 

 

合理使用Native 方法

在APP里面使用本地方法未必比使用java更加高效。其一是,在java和native的转换上会有时间的损耗,然后JIT不能优化。如果你分配本地资源(内存在本地堆,文件描述),很明显更难地分配这些资源的集合。

你也需要编译你的代码到你想要的各个平台,放在G1上,NEXUS one上等等。本地代码很有用,当你想部署程序到存在的本地代码库的android平台上,不是为了加速android里面ava语言部分。

 

性能秘诀

在一个没有JIT的设备上,如果使用一个变量的准确类型比使用一个接口更加高效(例如使用hashmap比map更好)大约6%的慢,但是JIT使两者没什么区别。

 

在一个没有JIT的设备上,缓存变量访问会比频繁访问快20%的速度。使用JIT,变量访问和本地访问的速度是一样的,这不值得优化,除非你想让你的代码更加容易阅读。

 

保持计算

当你开始优化之前,你必须确定你有一个问题需要解决。保证你能准确计算当前的性能,不然你不会知道你试图尝试的方法的好处。

 

这个文档的每一个主张都有基准测试作为保证,基准测试的代码可以在code.google.com "dalvik" project.上找到

 

基准测试是建立在caliper的微基准测试java框架上,微基准测试很难去准确,所以caliper帮助你做了其中困难的部分,甚至检测了一些情况你没有计算到,但是认为自己计算到了(虚拟机一直在帮你完成这个)。我们建议你使用caliper来运行你自己的微基准测试。

 

你可能会发现Traceview是非常方便剖析的,但是你必须知道目前的情况是JIT是未被启用的,可能会导致吧时间浪费在代码上,JIT可能会赢回来的。所以非常重要,在traceview上建议的一些改变去保证结果数据比没有traceview跑的更快。

 

有空再翻译JNI TIPS

http://developer.android.com/training/articles/perf-jni.html 

pro guard

http://developer.android.com/tools/help/proguard.html