地址

笔记45 | 代码性能优化建议[转]


目录

  • 前言
  • 避免创建不必要的对象
  • 选择Static而不是Virtual
  • 常量声明为Static Final
  • 避免内部的Getters/Setters
  • 使用增强的For循环
  • 使用包级访问而不是内部类的私有访问
  • 避免使用float类型
  • 使用库函数
  • 谨慎使用native函数
  • 关于性能的误区

前言

通常来说,高效的代码需要满足下面两个原则:
a. 不要做冗余的工作
b. 尽量避免执行过多的内存分配操作
在优化App时其中一个难点就是让App能在各种型号的设备上运行。不同版本的虚拟机在不同的处理器上会有不同的运行速度。你甚至不能简单的认为“设备X的速度是设备Y的F倍”,然后还用这种倍数关系去推测其他设备。另外,在模拟器上的运行速度和在实际设备上的速度没有半点关系。同样,设备有没有JIT也对运行速度有重大影响:在有JIT情况下的最优化代码不一定在没有JIT的情况下也是最优的。

避免创建不必要的对象

创建对象从来不是免费的。Generational GC可以使临时对象的分配变得廉价一些,但是执行分配内存总是比不执行分配操作更昂贵。

随着你在App中分配更多的对象,你可能需要强制gc,而gc操作会给用户体验带来一点点卡顿。虽然从Android 2.3开始,引入了并发gc,它可以帮助你显著提升gc的效率,减轻卡顿,但毕竟不必要的内存分配操作还是应该尽量避免。

因此请尽量避免创建不必要的对象,有下面一些例子来说明这个问题:

如果你需要返回一个String对象,并且你知道它最终会需要连接到一个StringBuffer,请修改你的函数实现方式,避免直接进行连接操作,应该采用创建一个临时对象来做字符串的拼接这个操作。
当从已经存在的数据集中抽取出String的时候,尝试返回原数据的substring对象,而不是创建一个重复的对象。使用substring的方式,你将会得到一个新的String对象,但是这个string对象是和原string共享内部char[]空间的。
一个稍微激进点的做法是把所有多维的数据分解成一维的数组:

一组int数据要比一组Integer对象要好很多。可以得知,两组一维数组要比一个二维数组更加的有效率。同样的,这个道理可以推广至其他原始数据类型。
如果你需要实现一个数组用来存放(Foo,Bar)的对象,记住使用Foo[]与Bar[]要比(Foo,Bar)好很多。(例外的是,为了某些好的API的设计,可以适当做一些妥协。但是在自己的代码内部,你应该多多使用分解后的容易)。
通常来说,需要避免创建更多的临时对象。更少的对象意味者更少的gc动作,gc会对用户体验有比较直接的影响。


选择Static而不是Virtual

如果你不需要访问一个对象的值,请保证这个方法是static类型的,这样方法调用将快15%-20%。这是一个好的习惯,因为你可以从方法声明中得知调用无法改变这个对象的状态。


常量声明为Static Final

考虑下面这种声明的方式

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

编译器会使用一个初始化类的函数,然后当类第一次被使用的时候执行。这个函数将42存入intVal,还从class文件的常量表中提取了strVal的引用。当之后使用intVal或strVal的时候,他们会直接被查询到。

我们可以用final关键字来优化:

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

这时再也不需要上面的方法了,因为final声明的常量进入了静态dex文件的域初始化部分。调用intVal的代码会直接使用42,调用strVal的代码也会使用一个相对廉价的“字符串常量”指令,而不是查表。

Notes:这个优化方法只对原始类型和String类型有效,而不是任意引用类型。不过,在必要时使用static final是个很好的习惯。


避免内部的Getters/Setters

像C++等native language,通常使用getters(i = getCount())而不是直接访问变量(i = mCount)。这是编写C++的一种优秀习惯,而且通常也被其他面向对象的语言所采用,例如C#与Java,因为编译器通常会做inline访问,而且你需要限制或者调试变量,你可以在任何时候在getter/setter里面添加代码。

然而,在Android上,这不是一个好的写法。虚函数的调用比起直接访问变量要耗费更多。在面向对象编程中,将getter和setting暴露给公用接口是合理的,但在类内部应该仅仅使用域直接访问。

在没有JIT(Just In Time Compiler)时,直接访问变量的速度是调用getter的3倍。有JIT时,直接访问变量的速度是通过getter访问的7倍。

请注意,如果你使用ProGuard,你可以获得同样的效果,因为ProGuard可以为你inline accessors.


使用增强的For循环

增强的For循环(也被称为 for-each 循环)可以被用在实现了 Iterable 接口的 collections 以及数组上。使用collection的时候,Iterator会被分配,用于for-each调用hasNext()和next()方法。使用ArrayList时,手写的计数式for循环会快3倍(不管有没有JIT),但是对于其他collection,增强的for-each循环写法会和迭代器写法的效率一样。

请比较下面三种循环的方法:

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-each。
所以请尽量使用for-each的方法,但是对于ArrayList,请使用方法one()。


使用包级访问而不是内部类的私有访问

参考下面一段代码

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”。

问题是,VM因为Foo和FooInner是不同的类,会认为在FooInner中直接访问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()函数时,它都会调用这些静态方法。这意味着,上面的代码可以归结为,通过accessor函数来访问成员变量。早些时候我们说过,通过accessor会比直接访问域要慢。所以,这是一个特定语言用法造成性能降低的例子。

如果你正在性能热区(hotspot:高频率、重复执行的代码段)使用像这样的代码,你可以把内部类需要访问的域和方法声明为包级访问,而不是私有访问权限。不幸的是,这意味着在相同包中的其他类也可以直接访问这些域,所以在公开的API中你不能这样做。


避免使用float类型

Android系统中float类型的数据存取速度是int类型的一半,尽量优先采用int类型。

就速度而言,现代硬件上,float 和 double 的速度是一样的。空间而言,double 是两倍float的大小。在空间不是问题的情况下,你应该使用 double 。

同样,对于整型,有些处理器实现了硬件几倍的乘法,但是没有除法。这时,整型的除法和取余是在软件内部实现的,这在你使用哈希表或大量计算操作时要考虑到。


使用库函数

除了那些常见的让你多使用自带库函数的理由以外,记得系统函数有时可以替代第三方库,并且还有汇编级别的优化,他们通常比带有JIT的Java编译出来的代码更高效。典型的例子是:Android API 中的 String.indexOf(),Dalvik出于内联性能考虑将其替换。同样 System.arraycopy()函数也被替换,这样的性能在Nexus One测试,比手写的for循环并使用JIT还快9倍。


谨慎使用native函数

结合Android NDK使用native代码开发,并不总是比Java直接开发的效率更好的。Java转native代码是有代价的,而且JIT不能在这种情况下做优化。如果你在native代码中分配资源(比如native堆上的内存,文件描述符等等),这会对收集这些资源造成巨大的困难。你同时也需要为各种架构重新编译代码(而不是依赖JIT)。你甚至对已同样架构的设备都需要编译多个版本:为G1的ARM架构编译的版本不能完全使用Nexus One上ARM架构的优势,反之亦然。

Native 代码是在你已经有本地代码,想把它移植到Android平台时有优势,而不是为了优化已有的Android Java代码使用。

如果你要使用JNI,请学习JNI Tips


关于性能的误区

在没有JIT的设备上,使用一种确切的数据类型确实要比抽象的数据类型速度要更有效率(例如,调用HashMap map要比调用Map map效率更高)。有误传效率要高一倍,实际上只是6%左右。而且,在JIT之后,他们直接并没有大多差异。

在没有JIT的设备上,读取缓存域比直接读取实际数据大概快20%。有JIT时,域读取和本地读取基本无差。所以优化并不值得除非你觉得能让你的代码更易读(这对 final, static, static final 域同样适用)。


我的Android征途

转载于:https://www.cnblogs.com/xiangevan/p/10751672.html

笔记45 | 代码性能优化建议[转]相关推荐

  1. java代码统计收藏量_干货收藏 | 35个Java 代码性能优化总结(上)

    原标题:干货收藏 | 35个Java 代码性能优化总结(上) 前言 代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这 ...

  2. 44个Java代码性能优化总结

    转载自 44个Java代码性能优化总结 代码优化的最重要的作用应该是:避免未知的错误.在代码上线运行的过程中,往往会出现很多我们意想不到的错误,因为线上环境和开发环境是非常不同的,错误定位到最后往往是 ...

  3. Windows Phone性能优化建议

    Windows Phone性能优化建议 原文:Windows Phone性能优化建议 使用background thread解码图片 在Windows Phone中支持的图片格式有jpg和png,微软 ...

  4. Android 系统性能优化(42)---Android代码内存优化建议-Android资源篇

    Android代码内存优化建议-Android资源篇 这篇文章主要介绍在实际Android应用程序的开发中,容易导致内存泄露的一些情况.开发人员如果在进行代码编写之前就有内存泄露方面的基础知识,那么写 ...

  5. Android 系统性能优化(41)---Android代码内存优化建议-OnTrimMemory优化

    Android代码内存优化建议-OnTrimMemory优化 OnTrimMemory 回调是 Android 4.0 之后提供的一个API,这个 API 是提供给开发者的,它的主要作用是提示开发者在 ...

  6. 【Java】44个Java代码性能优化总结

    1.概述 转载:44个Java代码性能优化总结 代码优化的最重要的作用应该是:避免未知的错误.在代码上线运行的过程中,往往会出现很多我们意想不到的错误,因为线上环境和开发环境是非常不同的,错误定位到最 ...

  7. python代码性能优化技巧

    python代码性能优化技巧 代码优化能够让程序运行更快,可以提高程序的执行效率等,对于一名软件开发人员来说,如何优化代码,从哪里入手进行优化?这些都是他们十分关心的问题.本文着重讲了如何优化Pyth ...

  8. oracle 优化建议,oracle 性能优化建议小结

    原则一:注意WHERE子句中的连接顺序:ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前,那些可以过滤掉最大数量记录的条件必须写在WHERE子 ...

  9. java 代码性能优化_Java代码性能优化的几个小技巧

    Java代码性能优化的几个小技巧 时间:2017-08-07     来源:华清远见JAVA学院 代码优化是程序员必须懂得一门学问,所以不管是程序员还是准程序员,养成良好的代码优化习惯都是必须要养成的 ...

最新文章

  1. LoadRunner11录制脚本出现的问题
  2. windows server 2008虚拟化技术一览
  3. [数字信号处理]IIR滤波器的间接设计(C代码)(转)
  4. 七年级计算机室使用计划表,七年级信息技术教学工作计划
  5. 作者:杨婧(1983-),女,博士,中国科学院计算技术研究所工程师。
  6. Undefined function 'adb' for input arguments of type 'double'.
  7. Dart教程(二):基本语法
  8. Vue源码分析系列:目录
  9. html怎么画一个正方形,css3画一个正方体
  10. YOLO7 姿势识别实例
  11. 一个屌丝程序猿的人生(一百二十)
  12. python3socket非阻塞在linux里无效_Linux Socket - 内核非阻塞功能
  13. 通过Windows批处理脚本批量修改DNS
  14. Postgresql12 安装及设置远程访问
  15. 虚拟相机IOS手机端安装教程
  16. 和平精英分数计算机制,和平精英段位对应的积分完整一览 和平精英多少分上王牌...
  17. 灵魂显示正在登录聊天服务器,Soul跟随灵魂找到你
  18. 大学生IT博客大赛最技术50强与最生活10强文章
  19. 基于诺诺电子发票平台开发开具电子发票
  20. 陕西省高级职称评审的要求条件和流程全新版

热门文章

  1. Intellij新建Spring项目引入用户目录下的Spring jar包
  2. 【hdoj_2152】Fruit(母函数)
  3. 解决Sublime Text3莫名的中文乱码问题
  4. ARM的编程模式和7种模式
  5. RAC+ASM在单机上恢复的过程
  6. ASP.net Table 控件
  7. 设置 Linux 的 LD_LIBRARY_PATH 变量
  8. 自由口通信模式下计算机读写PLC存储区的程序
  9. 91.91p10.space v.php,luogu P1091 合唱队形
  10. AUTOSAR从入门到精通100讲(三十八)-AUTOSAR 通信服务 - NM概念详解