这份文件主要涉及微优化组合时,可以提高应用程序的整体性能,但它不太可能,这些变化将导致显着的性能影响。选择合适的算法和数据结构应该永远是你的优先级,但本文档的范围之内。您应该使用提示本文档中作一般的编码实践,你可以融入你的习惯,一般的代码效率。
有编写高效代码的两条基本规则:
其中,当微优化的Android应用程序你将面对的最棘手的问题是,你的应用程序是一定要在多种类型的硬件上运行。运行在不同的速度运行不同的处理器不同版本的虚拟机。它也不是一般,你可以简单地说:“设备X是一个系数F更快/比装置Y慢”了,从一个设备扩展你的结果给他人的情况。特别是,在模拟器上测试会告诉你很了解在任何设备上的性能。也有有和没有一个设备之间的巨大差异 JIT:与JIT的设备最好的代码并不总是一个设备最好的代码没有。
为确保您的应用程序以及执行跨多种设备,确保你的代码是有效的各级致力经营优化性能。
对象的创建从来都不是免费的。分代垃圾收集器,每个线程分配池的临时对象可以使分配更便宜,但是分配的内存总是比不分配内存更贵。
正如你在你的应用程序分配更多的对象,你会强制定期的垃圾收集,在用户体验创造一点“hiccups”。在Android 2.3的推出了并发垃圾收集器的帮助,但应始终避免不必要的工作。
因此,你应该避免创建你不需要对象实例。的东西,可以帮助一些例子:
StringBuffer
反正,改变你的签名和实施,使该函数不直接追加,而不是建立一个短命的临时对象。String
对象,但将共享的char []
的数据。(权衡的是,如果你只使用原始输入的一小部分,你会保持各地在内存无论如何,如果你走这条路线。)一个有些更激进的想法是切片多维数组转换成并行的单一维数组:
Integer对象,但是这也可以推广到一个事实,即整数的两个平行的阵列也有很多比数组更有效(int,int)的
对象。这同样适用于基本类型的任意组合。(Foo,Bar)
对象,要记住,两个平行
Foo[]
and Bar[]
数组通常比自定义的单个阵列好得多(Foo,Bar)
对象。(唯一的例外,当然,是当你设计其他代码的API来访问。在这种情况下,它通常是更好的做一个小的妥协的速度,以达到良好的API设计的,但在你的自己内部的代码,你应该尝试,并尽可能高效。)一般来说,避免如果你能创造短期的临时对象。更少的对象创建的平均值不太频繁的垃圾收集,这对用户体验有直接的影响。
如果你不需要访问一个对象的字段,让您的方法是静态的。调用将有约15%-20%的速度。这也是很好的做法,因为你可以从方法签名告诉调用该方法不能改变对象的状态。
考虑下面的声明在类的顶部:
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
常量,不是任意的引用类型。不过,这是很好的做法,声明常量静态最终
只要有可能。
在像C + +的母语是很常见的做法是使用getter方法(个GetCount()I =
),而不是直接访问字段(I = mCount
)。这是一个很好的习惯,C + +和经常实行其他面向对象的语言,如C#和Java,因为编译器通常可以内联访问,如果你需要限制或调试现场访问,您可以在任何时候添加代码。
然而,这是Android上的一个坏主意。虚方法调用是昂贵的,远远超过了实例字段查找。这是合理的,遵循共同的面向对象的编程实践,并有getter和setter的公共接口,而是一个类中,你应该总是直接访问字段。
如果没有JIT,直接字段访问大约是3倍比调用一个微不足道的getter更快。随着JIT(其中直接字段访问是便宜,因为访问本地),直接字段访问大约是7倍比调用一个微不足道的getter更快。
请注意,如果你使用ProGuard的,你可以有两全其美的,因为ProGuard的内联可以访问你。
加强对
循环(有时也被称为“的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
提升2倍大。与台式机,假设空间不是问题,你应该更喜欢double
到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,而不是为“加快”你的Android应用程序的某些部分写入Java语言中一个现有的本地代码库的本机代码,主要是有用的。
如果您确实需要使用本机代码,你应该阅读我们 的JNI提示。
提示: 另请参见乔希布洛赫的有效的Java,项目54。
在没有JIT的设备,这是事实,通过一个变量调用方法有一个确切的类型,而不是一个接口会更有效。(因此,举例来说,它是便宜的来调用一个方法 HashMap map
比Map map
,,即使在两种情况下的映射是一个
HashMap中
。)它是不是这种情况,这是2倍速度较慢; 实际的差别是慢更像是6%。此外,JIT使这两个有效的区分。
在没有JIT的设备,缓存字段访问比反复accesssing现场快20%左右。使用JIT,约等于本地访问字段访问的成本,所以这不是一个值得优化,除非你觉得它使你的代码更易于阅读。(这是最终的,静态的,静态final字段也如此。)
在您开始优化,确保你有你需要解决的一个问题。确保你能准确地测量您现有的表现,否则你将无法衡量你尝试替代品的好处。
本文件中提出的每项索赔背后是一个标杆。该人士对这些指标可以在找到code.google.com“的Dalvik”项目。
基准的建立是在 显卡 microbenchmarking的Java框架。微基准是很难得到正确的,所以显卡超出它的方式去进行艰苦的工作适合你,甚至发现一些情况下,你不测量你认为你测量(因为,比如说,虚拟机已经成功地优化所有的代码了)。我们强烈建议您使用显卡来运行自己的微基准。
你可能还会发现 Traceview对分析很有用,但要认识到它目前禁用JIT的,这可能导致其misattribute时间编写的JIT可能能够赢回这一点很重要。尤其重要的是使通过Traceview数据修改建议,以确保生成的代码实际运行得更快而不Traceview运行时之后。
如需更多帮助,分析和调试你的应用程序,请参阅下列文档: