Android最佳实践性能(二)性能提示

来源:互联网 发布:焦大seo教程 编辑:程序博客网 时间:2024/05/22 02:23

性能提示

在本文档中

  1. 避免创建不必要的对象
  2. 喜欢静态虚拟以上
  3. 使用static final的常量
  4. 避免内部getter / setter方法
  5. 使用增强的for循环语法
  6. 考虑而不是与私人内部类私接包
  7. 避免使用浮点数
  8. 了解和使用库
  9. 使用本地方法小心
  10. 使用本地方法明智地
  11. 注闭幕

这份文件主要涉及微优化组合时,可以提高应用程序的整体性能,但它不太可能,这些变化将导致显着的性能影响。选择合适的算法和数据结构应该永远是你的优先级,但本文档的范围之内。您应该使用提示本文档中作一般的编码实践,你可以融入你的习惯,一般的代码效率。

有编写高效代码的两条基本规则:

  • 不这样做,你不需要做的工作。
  • 不分配内存,如果你能避免它。

其中,当微优化的Android应用程序你将面对的最棘手的问题是,你的应用程序是一定要在多种类型的硬件上运行。运行在不同的速度运行不同的处理器不同版本的虚拟机。它也不是一般,你可以简单地说:“设备X是一个系数F更快/比装置Y慢”了,从一个设备扩展你的结果给他人的情况。特别是,在模拟器上测试会告诉你很了解在任何设备上的性能。也有有和没有一个设备之间的巨大差异 JIT:与JIT的设备最好的代码并不总是一个设备最好的代码没有。

为确保您的应用程序以及执行跨多种设备,确保你的代码是有效的各级致力经营优化性能。

避免创建不必要的对象


对象的创建从来都不是免费的。分代垃圾收集器,每个线程分配池的临时对象可以使分配更便宜,但是分配的内存总是比不分配内存更贵。

正如你在你的应用程序分配更多的对象,你会强制定期的垃圾收集,在用户体验创造一点“hiccups”。在Android 2.3的推出了并发垃圾收集器的帮助,但应始终避免不必要的工作。

因此,你应该避免创建你不需要对象实例。的东西,可以帮助一些例子:

  • 如果你有一个方法返回一个字符串,你知道它的结果总是会被附加到一个StringBuffer反正,改变你的签名和实施,使该函数不直接追加,而不是建立一个短命的临时对象。
  • 当提取一组输入数据的字符串,试图返回,而不是创建一个副本的原始数据的一个子串。您将创建一个新String对象,但将共享的char [] 的数据。(权衡的是,如果你只使用原始输入的一小部分,你会保持各地在内存无论如何,如果你走这条路线。)

一个有些更激进的想法是切片多维数组转换成并行的单一维数组:

  • 数组ints的值比数组更好Integer对象,但是这也可以推广到一个事实,即整数的两个平行的阵列也有很多比数组更有效int,int)的 对象。这同样适用于基本类型的任意组合。
  • 如果你需要实现一个容器,它存储的元组Foo,Bar 对象,要记住,两个平行Foo[] and Bar[]数组通常比自定义的单个阵列好得多Foo,Bar对象。(唯一的例外,当然,是当你设计其他代码的API来访问。在这种情况下,它通常是更好的做一个小的妥协的速度,以达到良好的API设计的,但在你的自己内部的代码,你应该尝试,并尽可能高效。)

一般来说,避免如果你能创造短期的临时对象。更少的对象创建的平均值不太频繁的垃圾收集,这对用户体验有直接的影响。

喜欢静态虚拟以上


如果你不需要访问一个对象的字段,让您的方法是静态的。调用将有约15%-20%的速度。这也是很好的做法,因为你可以从方法签名告诉调用该方法不能改变对象的状态。

使用static final的常量


考虑下面的声明在类的顶部:

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

编译器会生成一个类初始化方法,叫做 <clinit>,当第一次使用类时执行。该方法存储值42到INTVAL,并提取从类文件中的字符串常数表的参考strVal。当这些值被引用后,他们被访问与字段的查找。

我们可以改善的事项与“最终”的文章:

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

类不再需要<clinit>方法,因为常量进入静态字段初始值设定项中的dex文件。代码是指INTVAL将直接使用整数值42,并访问strVal将使用相对便宜的“字符串常量”的指令,而不是一个字段查找。

注意:这个优化只适用于基本类型和 String常量,不是任意的引用类型。不过,这是很好的做法,声明常量静态最终只要有可能。

避免内部getter / setter方法


在像C + +的母语是很常见的做法是使用getter方法(个GetCount()I =),而不是直接访问字段(I = mCount)。这是一个很好的习惯,C + +和经常实行其他面向对象的语言,如C#和Java,因为编译器通常可以内联访问,如果你需要限制或调试现场访问,您可以在任何时候添加代码。

然而,这是Android上的一个坏主意。虚方法调用是昂贵的,远远超过了实例字段查找。这是合理的,遵循共同的面向对象的编程实践,并有getter和setter的公共接口,而是一个类中,你应该总是直接访问字段。

如果没有JIT,直接字段访问大约是3倍比调用一个微不足道的getter更快。随着JIT(其中直接字段访问是便宜,因为访问本地),直接字段访问大约是7倍比调用一个微不足道的getter更快。

请注意,如果你使用ProGuard的,你可以有两全其美的,因为ProGuard的内联可以访问你。

使用增强的for循环语法


加强循环(有时也被称为“的for-each”循环),可用于实现集合的Iterable接口和阵列。随着收藏品,一个迭代器被分配做接口调用的hasNext()next()方法。随着ArrayList中,手写计数循环大约是3倍速度更快(带或不带JIT),但对于其他收藏品增强的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的设备。它采用增强的for Java编程语言的1.5版本中引入循环语法。

所以,你应该使用增强循环默认,但考虑手写循环计数为性能关键的ArrayList迭代。

提示: 另请参见乔希布洛赫的有效的Java,项目46。

考虑而不是与私人内部类私接包


考虑下面的类定义:

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)直接访问外部类的私有方法和私有实例字段。这是合法的,并且代码打印“值27”如预期。

问题是,虚拟机认为直接访问 Foo$Inner是非法的,因为 Foo and Foo$Inner有不同的类,尽管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()在外部类的方法。这句话的意思是,上面的代码中真正归结到你通过存取方法访问成员字段的情况。前面我们谈到了如何存取速度较慢比直接字段访问,所以这是一个特定的语言成语造成一个“看不见”的性能影响的一个例子。

如果你使用这样的代码在性能热点,您可以通过内部类访问的有包访问声明字段和方法,而不是私人的访问避免了开销。不幸的是,这意味着该字段可以直接被其他类在同一个包访问,所以你不应该在公共API使用。

避免使用浮点数


作为一个经验法则,浮点约2倍比Android手机整数慢。

在速度方面,有没有什么区别float和  double上更现代的硬件。空间方面, double 提升2倍大。与台式机,假设空间不是问题,你应该更喜欢 double float

此外,即使是整数,有些处理器具有硬件乘法但缺乏硬件除法。在这种情况下,整数除法和取模操作都是在软件的东西进行思考,如果你正在设计一个哈希表或者做大量的数学。

了解和使用库


除了 ​​所有常用的理由更喜欢库代码在滚动你自己,记住,该系统是可以自由更换调用库方法与手工编码的汇编,这可能比JIT可以产生最好的代码更好相应的Java。这里典型的例子是String.indexOf()和相关的API,它的Dalvik替换内联征。同样的,System.arraycopy()方法是关于9x中比在Nexus One的使用JIT一个手工编码的循环快。

提示: 另请参见乔希布洛赫的有效的Java,项目47。

使用本地方法小心


使用与开发本地代码你的应用程序 Android NDK  不一定比用Java语言编程的效率。一方面,有使用Java本机过渡相关的成本,以及JIT不能跨越这些边界优化。如果你分配本 ​​地资源(内存本机堆,文件描述符,或其他),它可以显著更难安排及时收集这些资源。你还需要编译你的代码,你想上(而不是依赖于有一个JIT吧)运行的每个架构。你甚至可能需要编译多个版本为你考虑相同的体系结构:编译在G1的ARM处理器原生代码不能充分利用了ARM在Nexus One,并在Nexus One编译为ARM代码在G1上的ARM将无法运行。

当你有你想要的端口到Android,而不是为“加快”你的Andr​​oid应用程序的某些部分写入Java语言中一个现有的本地代码库的本机代码,主要是有用的。

如果您确实需要使用本机代码,你应该阅读我们 的JNI提示。

提示: 另请参见乔希布洛赫的有效的Java,项目54。

性能神话


在没有JIT的设备,这是事实,通过一个变量调用方法有一个确切的类型,而不是一个接口会更有效。(因此,举例来说,它是便宜的来调用一个方法 HashMap mapMap map,,即使在两种情况下的映射是一个HashMap中。)它是不是这种情况,这是2倍速度较慢; 实际的差别是慢更像是6%。此外,JIT使这两个有效的区分。

在没有JIT的设备,缓存字段访问比反复accesssing现场快20%左右。使用JIT,约等于本地访问字段访问的成本,所以这不是一个值得优化,除非你觉得它使你的代码更易于阅读。(这是最终的,静态的,静态final字段也如此。)

经常测量


在您开始优化,确保你有你需要解决的一个问题。确保你能准确地测量您现有的表现,否则你将无法衡量你尝试替代品的好处。

本文件中提出的每项索赔背后是一个标杆。该人士对这些指标可以在找到code.google.com“的Dalvik”项目。

基准的建立是在 显卡 microbenchmarking的Java框架。微基准是很难得到正确的,所以显卡超出它的方式去进行艰苦的工作适合你,甚至发现一些情况下,你不测量你认为你测量(因为,比如说,虚拟机已经成功地优化所有的代码了)。我们强烈建议您使用显卡来运行自己的微基准。

你可能还会发现 Traceview对分析很有用,但要认识到它目前禁用JIT的,这可能导致其misattribute时间编写的JIT可能能够赢回这一点很重要。尤其重要的是使通过Traceview数据修改建议,以确保生成的代码实际运行得更快而不Traceview运行时之后。

如需更多帮助,分析和调试你的应用程序,请参阅下列文档:

  • 与Traceview和dmtracedump分析
  • 试析显示和性能的systrace
0 0
原创粉丝点击