性能优化小技巧

https://developer.android.com/training/articles/perf-tips.html

这篇文章主要讲述了一些小优化,但是如果把这些小优化都结合起来的话则会提高一个app的整体性能。不过这也不代表对于性能它们会有质的改变。首当其冲是选择正确的算法和数据结构,不过它不在本篇文章的讨论范围内。你应该将本篇文章讨论的小技巧融入到编码习惯中以提示通用编码效率。

编写有效率的代码,有两个基本的原则:

  • 不要做太多不需要你做的事
  • 不要使用你可以不用的内存

当对一个app进行细微优化时最棘手的问题是app一定会被运行在各种各样的硬件上。不同版本的VM运行在不同的处理上以不同的运行速度。它不是一种通用的情况,你可以简单的说“设备X因为什么原因导致了比设备Y快/慢了”,并且可以把结果从一个设备扩展到另外一个设备。对于有没有JIT的设备也会有巨大的差别。有JIT的设备的最好的编码方式对于没有JIT的设备来说通常不是最好的。

为了确保app在各种各样的设备上都表现良好,需要确保你的代码对于各个版本都是高效的,并且要积极的优化性能

避免创建无用的对象

创建对象总是有代价的。一个为临时对象创建的含线程分配池的一代GC使得代价减小,但是分配内存总是比不分配内存代价更高

当在app分配很多对象时,会触发周期性的GC,用户体验中出现卡顿。异步的GC已经在Android2.3引入,但是不必要的工作还是应该避免

因此,应该避免创建不需要的对象,下面的一些例子或许有些帮助:

  • 如果你需要一个返回string的方法,那么结果无论如何应该是StringBuffer,改变函数的定义和实现使得函数可以直接返回而不是创建一个临时对象
  • 当从原始数据提取字符串时,应该返回原始数据的子串而不是创建一个拷贝。你将会创建一个新的String对象,但是它会同原始数据共享char[] (需要权衡的是如果你只需要原始数据的一小部分,如果你这么做,你会在内存中一直持有它)

一些更激进的做法是切割一个多维数组为单个的一维数组:

  • 一个int的数组比Integer的数组更好,两个一维的int数组比(int,int)二维数组效率更高。对于其他原始数据类型这个原则也适用
  • 如果你需要实现一个存储(Foo,Bar)的容器,记着Foo[]和Bar[]数组比(Foo,Bar)更好。(当然也有例外情况,就是当你为其它代码设计一个API时,这时,为了更好的API设计做一些速度上的妥协更好)。但是在你自己的代码中,应该尽量的使用更高效的代码

通常,尽一切可能避免创建临时的对象,更少的对象创建意味着更少频次的GC,而GC会直接影响用户体验

使用静态优于虚拟

如果不需要访问对象的字段,使用静态方法,调用会快15%~20%。这也是一个很好的实践,因为你可以从方法签名中区分并且调用方法不会改变对象的状态

常量用static final修饰

考虑下面几种在类开头的声明

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

编译器会生成一个class初始化方法,叫,它会在类一开始使用的时候执行。这个方法在intVal存储了一个值42,为strVal在类文件字符串常量表中提取了一个引用。当这些值后面调用时,可以直接被属性引用。

我们可以使用’final‘关键字改进代码

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

这个类将不再需要一个方法,因为这些常量已经直接写入了dex文件的静态属性初始化中。intVal可以直接使用42,访问strVal也可以使用代价没那么大的’string constant’而不是属性调用

需要注意:这个优化只适用原始数据类型和string变量,而不是任意类型。当然在任何可能的情况下使用static final都是一个好的实践

避免内部使用Getters/Setters

一些本地语言如C++通常会使用getter(i=getCount())而不是直接访问变量(i = mCount).对C++来说是一个非常好的习惯,并且在经常在其它面向对象的语言如C#和Java中使用,由于编译器可以内嵌的访问并且如果你需要限制或调试代码访问权限,可以在任何时候添加

然而,对于Android这是一个不好的习惯,虚拟方法的调用比实例属性查询的代价要更大,遵循面向对象编程的实践在公共接口地方使用get set方法是有充分理由的。但是在一个类内部你还是应该直接访问属性

如果没有JIT,直接访问属性比调用get方法快3x多。如果有JIT(直接访问属性的代价跟访问本地的一样),直接访问属性比调用get方法快7x多

如果你使用ProGuard,你可以两全其美因为ProGuard为你内置了访问器

使用增强的Loop语法

增强的loop(如 for-each loop)可以用于实现了Iterable的集合和数组。集合需要实现接口 hasNext()和next()方法。对ArrayList比手写的loop快3x(有货没有JIT都一样),但是对于其它的集合增强型的loop完全等同于显示迭代器的使用

下面是一些遍历数组的方案:

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

zer()是效率最低的,因为在整个循环中JIT无法优化每次循环获取数组大小的花销

one()相对会快些,它把所有的都变成了局部变量,避免了查找,只有数组长度提供了性能上的优势

two()是最快的对于没有JIT的设备,而对于有JIT的设备来说同one()一样。它使用了Java 1.5之后引入的增强的loop语法

可以查看Josh Bloch’s Effective Java, item 46

使用Package代替使用私有内联类

考虑以下的定义:

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)可以在外部类中直接访问一个私有的方法和一个私有的实例属性。这是合法的,这个代码如期打印了’Value is 27‘

问题是虚拟机认为通过FooInner直接访问Foo的私有成员是非法的,因为Foo和FooInner是不同的类,尽管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()方法的地方调用这些静态方法,这就意味着上面提到的代码如果真的可以归结为一种情况的话就是你可以通过方法访问成员变量。前面提到的访问器是怎么比直接访问属性慢,这是一个由于语言习惯导致的不可见的性能影响

如果在一个性能热点的地方使用这样的代码,可以避免声明变量和方法的开销通过访问有package访问权限的内部类而不是私有访问权限。不幸的是这意味着属性可以直接被在相同包下面的其它类访问,所以应该在公共API中避免这么做

避免使用浮点型

根据经验,在Android设备上浮点型数据比整型慢2x

在速度方面,在更现代的硬件上float和double没有什么区别,在空间占用上,double占用2x甚至更多。就像台式机一样,如果空间不是问题,应该使用double而不是float

同样的,即使对于整型,一些处理器有硬件乘法但是没有除法。对于这种情况,整型的除法和取模运算都是通过软件实现的-如果你要设计一个hash表或者做很多的数学运算需要考虑这些事情

了解并且会使用下面这些库

除了通常的原因应该使用库而不是自己实现,需要记住的是系统可以自由的使用手动编码替代库方法调用,它可能比JIT能为Java提供的最好的代码还要好。一个典型的例子是String.indexOf()和其它相关的APIs,Dalvik会用内联的内在函数替代,同样的,System.arraycopy()方法比一个有JIT Nexus One手动写的loop快9x

可以查看Josh Bloch’s Effective Java, item 47

谨慎的使用本地方法

使用Android NDK编写本地代码不一定会比用Java编写更有效。一方面,对于Java-native的转换会有一定的代价,并且JIT无法绕过边界去做优化。如果正在申请本地资源(本地堆内存、文件描述符或者其它任何东西),对于这些资源的回收是非常困难的。并且你必须为相同体系的CPU编译不同的版本:为G1 ARM处理器编译本地代码可能无法充分利用Nexus One的ARM,反之亦然

本地代码是用于你将现有的本地代码库移植到Android 而不是用于Android app的提速

如果需要使用本地代码,可以阅读JNI Tips

可以查看 Josh Bloch’s Effective Java, item 54

性能神话

没有JIT的设备,使用一个准确的类型调用方法比用接口调用方法更高效(比如,用HashMap map比Map map调用方法代价更低,尽管这两种map都是HashMap),它实际不是慢一半,而是慢6%。此外有JIT的设备,难分优劣

没有JIT的设备,缓存字段的访问比重复字段访问快20%。有JIT的设备,域变量访问代价与本地变量访问代价一样,因此没必要去优化除非你感觉它可以使得你的代码更易读(对final,static,static final更优是确实的)

经常测量

在开始优化之前,确保你有一些问题要解决。确保你可以精确的测量现有的性能,否则你将无法测量你尝试的替代方案的好处

这篇文章的每一个声明都是有基准测试的,基准测试的源码可以参考code.google.com “dalvik” project.

这些基准是基于Caliper微基准Java框架建立的。微基准是很难得到正确的,所以Caliper为你做了超出它的方式,甚至检测了一些你想测量但是没有测量的(因为,VM会优化你所有的代码)。我们强烈推荐你使用Caliper运行你自己的微基准

或许你会发现TraceView对分析非常有用,但是需要知道的是它目前跟JIT不兼容,这可能导致一些时候JIT会表现更好。特别重要的是要确保用TraceView优化过的代码确实比没有用TraceView的代码快了

更多分析调试app的可以参考:

Profiling with Traceview and dmtracedump

Analyzing UI Performance with Systrace

时间: 2024-10-06 00:59:11

性能优化小技巧的相关文章

How Javascript works (Javascript工作原理) (十一) 渲染引擎及性能优化小技巧

个人总结:读完这篇文章需要20分钟,这篇文章主要讲解了浏览器中引擎的渲染机制. DOMtree       ----|   |---->  RenderTree CSSOMtree  ----| 这是 JavaScript 工作原理的第十一章. 迄今为止,之前的 JavaScript 工作原理系列文章集中于关注 JavaScript 语言本身的功能,在浏览器中的执行情况,如何优化等等. 然而,当在构建网络应用的时候,不仅仅只是编写自己运行的 JavaScript 代码.所编写的 JavaScri

sql性能优化小技巧(一)

关于sql条件匹配对执行效率影响测试 首先,创建一个标量函数create function ff_test() returns int as begin declare @i int=0 while(@i<100000000) set @i+=1 return @i end 其次,选定随意一张表,这里使用业务表mt_delegate 观察如下两种情形 1. select * from mt_delegate where procid=-1 or dbo.ff_test()>10000 2. s

asp.net的优化小技巧收集

在页面不需要交互的情况下可以禁用ViewState 1.页面整体禁用ViewState:在顶部<%Page>中EnableViewState="false"; 2.指定控件禁用ViewState:控件的EnableViewState属性设置为false; 完全不要ViewState则把页面中的form中的runat="server"去掉(极端,后果很严重,Button等一大部分服务端控件不能用)这种情况一般用在站内搜索功能的时候,因为没去掉的时候,地址栏

MySQL数据库性能优化的技巧和窍门

数据库表表面上存在索引和防错机制,然而一个简单的查询就会耗费很长时间.Web应用程序或许在开发环境中运行良好,但在产品环境中表现同样糟糕.如果你是个数据库管理员,你很有可能已经在某个阶段遇到上述情况.因此,本文将介绍对MySQL进行性能优化的技巧和窍门. 1.存储引擎的选择 如果数据表需要事务处理,应该考虑使用InnoDB,因为它完全符合ACID特性.如果不需要事务处理,使用默认存储引擎MyISAM是比较明智的.并且不要尝试同时使用这两个存储引擎.思考一下:在一个事务处理中,一些数据表使用Inn

一些JavaScript中的DOM的优化小技巧

在进行DOM优化时需要关注的问题有:修改DOM的时候,会引起页面的重排,重绘.因为JS是单线程执行的,那么在重排重绘的过程中可能会阻塞用户的操作.为了更好的用户体验,必须要严格控制这些操作. 一.对象集合 NodeList 当我们调用:getElementsByTagName,getElementsByName,getElementsByClassName的时候,返回的结果是一个NodeList,这个NodeList是实时的.如果你修改对应的html,那么NodeList中也会得到修改. 而且,

提高myeclipse性能的小技巧(个人整理)

提高myeclipse性能的小技巧,个人整理,还有很多不全的地方,希望大家提出,共同进步. 1.工具栏的设置 工具栏中有很多不常用的图标,可以通过windows->Customize Perspective自己选择. 2.启动项设置 myeclipse在启动时,会加载相关启动项,其中有很多是我们用不到的容器,我们可以通过相关配置来选择启动与否.如:我不用jboss这个容器,我就去掉勾选,这样,在启动时,就不会加载此容器. 3.设置默认编辑器 大家都碰到过这样的问题,打开一个jsp页面或者html

做OI题时的一些常用的常数优化小技巧

注意:本文所介绍的优化并不是算法上的优化,那个就非常复杂了,不同题目有不同的优化.笔者要说的只是一些实用的常数优化小技巧,很简单,虽然效果可能不那么明显,但在对时间复杂度要求十分苛刻的时候,这些小的优化对于帮助你成功卡常也是十分重要的.那么我们让切入正题吧. (1)inline放在自定义函数前 不要问为什么,加就行了!额,这个东西好像可以让你的函数有机会被计算机执行得稍微快一点,一般放在使用次数比较多的函数前,像check(),为sort()定制的CMP()等等,当然主函数前就不要放了...比如

125个提高网页可用性的优化小技巧(二)

125个提高网页可用性的优化小技巧(二) --安阳师范学院互联网+应用技术学院UI设计方向讲师 崔凯让常用功能和重要数据信息更接近用户预测用户的意图,让他们尽可能接近目标. △ 筛选出或跳至用户正在搜索的条目 △ 将用户常选项目列为默认选项△ 产品列表页上把重要数据信息展示出来很多时候用户需要像踩弹簧高跷杖一样,点击一个产品,查看信息,返回上一页,再反复操作以查看其他产品.这种设计的可用性差.应把重要信息直接放在主要页面,减少用户反复操作的次数.如果你怕这样页面会杂乱,也可以设计成鼠标悬停时显示

JavaScript性能优化小窍门实例汇总

JavaScript性能优化小窍门实例汇总在众多语言中,JavaScript已经占有重要的一席之地,利用JavaScript我们可以做很多事情 , 应用广泛.在web应用项目中,需要大量JavaScript的代码,将来也会越来越多. 但是由于JavaScript是一个作为解释执行的语言,而且它的单线程机制,决定了性能问题是JavaScript的弱点,也是开发者在写JavaScript的时候需注意的一个问题. 因为经常会遇到Web 2.0应用性能欠佳的问题,主因就是JavaScript性能不足,导