官方文档:Androi“.NET研究”d应用程序运行的性能设计

简介:   Android应用程序运行的移动设备受限于其运算能力,存储空间,及电池续航。由此,它必须是高效的。电池续航可能是一个促使你优化程序的原因,即使他看起来已经运行的足够快了。由于续航对用户的重要性,当电量耗损陡增时,意味这用户迟早会发现是由于你的程序。

  Android应用程序运行的移动设备受限于其运算能力,存储空间,及电池续航。由此,它必须是高效的。电池续航可能是一个促使你优化程序的原因,即使他看起来已经运行的足够快了。由于续航对用户的重要性,当电量耗损陡增时,意味这用户迟早会发现是由于你的程序。

  虽然这份文档主要包含着细微的优化,但这些绝不能成为你软件成败的关键。选择合适的算法和数据结构永远是你最先应该考虑的事情,但这超出这份文档之外。

  1. 介绍

  写出高效的代码有两条基本的原则:

  ◆ 不作没有必要的工作

  ◆ 尽量避免内存分配。

  2. 明智的优化

  这份文档是关于Android规范的细微优化,所以先确保你已经了解哪些代码需要优化,并且知道如何去衡量你所做修改所带来的效果(好或坏)。用开投资开发的时间是有限的,所以明智的时间规划很重要。

  这份文档同时确保你在算法和数据结构上作出最佳选择,同时考虑了API选择所带来的潜在影响。使用恰当的数据结构和算法比这里的任何建议都有价值,考虑API版本带来的影响会如实你选择更好的实现。

  当你优化Android程序时会遇到的一个棘手问题是确保你的程序能在不同的硬件平台上运行。不同版本的虚拟机在不同处理器上的运行速度各不相同。并且不是简单的设备A比设备B快或者慢,并针对一个设备与其他设备之间做出排列。特别的,模拟器上只能评测小部分可以在设备上体现的东西。有无JIT的设备间也有着巨大差异:对于有JIT设备好的代码有时对无JIT的设备并不是最好的。

  如果你想知道程序在设备上的表现,就必须在上面进行测试

  3. 避免创建不必要的对象

  对象创建永远不会免费的。每个线程的分代GC给临时对象分配一个地址池能降低分配开销,但分配内存往往需要比不分配内存高的代价。

  如果在用户界面周期内分配对象,会强制一个周期性的垃圾回收,给用户体验造成小小的停顿间隙。Gingerbread中介绍的并发回收也许有用,但应该避免不必要的工作。

  因此,避免创建不需要的对象实例。下面是几个例子:

  ◆ 如果有一个返回String的方法,他的返回值通常附加在一个StringBuffer上,改变声明和实现,这样函数直接在其后面附加,而非创建一个短暂存在的临时变量。

  ◆ 当从输入的数据集合中读取数据时,考虑返回原始数据的子串,而非新建一个拷贝。这样你会创建一个新的对象,但是他们共享该数据的char数组。换来的是即使你仅仅使用原始输入的一部分,你也需要保证它一直存在于内存中。

  一个更彻底的观点是将多维数组切割成一维数组:

  ◆ Int类型的数组比Integer类型的好。推而广之,两个平行的int数组要比一个(int,int)型的对象数组高效。这个定理对于任何基本数据类型的组合都通用。

  ◆ 如果需要实现存放元组(Foo,Bar)对象的容器,记住两个平行数组Foo[], Bar[]会优于一个(Foo,Bar)对象的数组。(例外情况是:当你设计API给其他代码调用时,最好用好的API设计来换取小的速度提升。但在自己的内部代码中,尽量尝试高效的实现。)

  通常来说,尽量避免创建短时临时对象。少的对象创建意味着低频的垃圾回收。这对于用户体验产生直接的影响。

  4. 性能之谜

  前一个版本的文档给出了好多误导人的主张,这里做一些澄清:

  ◆ 在没有JIT的设备上,调用方法所传递的对象采用具体的类型而非接口类型会更有效(比如,传递HashMap map比传递Map map调用一个方法耗费的开销小,尽管两种情况下的map都是HashMap)。但这并不是两倍慢的情形,事实上,只相差6%,而JIT使这两种调用的效率不分伯仲。

  ◆ 在没有JIT的设备上,访问缓存后的字段比直接访问字段快大概20%。在有JIT的情况下,字段访问和局部访问耗费是一样的 。所以这里不值得优化,除非你觉得他会让你的代码更易读(对于final,static,及static final 变量同样适用).

  5. 用静态代替虚拟

  如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法,因为你可以通过方法声明知晓调用该方法不需要更新此对象的状态。

  6. 避免内部的Getters/Setters

  在源生语言像C++中,通常做法是用Getters(i=getCount())代替直接访问字段(i=mCount)。这是C++中一个好的习惯,因为编译器会内联这些访问,如果需要约束或者调试这些域的访问,你可以在任何时间添加代码。

  在Android中,这是个不好的想法。虚方法调用代价比直接存取字段高昂的多。按照通常面向对象语言的做法在公共接口中使用Getters和Setters是有原因的,但应该在一个经常访问其字段的类中采用直接访问。

  无JIT时,直接字段访问大约比调用无关紧要的getter来访问快3倍。有JIT时(直接访问字段开销和访问局部变量是一样的),要快7倍。在Froyo版本中确实如此,但以后会在JIT中改进Getter方法的内联。

  7. 对常量使用Static Final修饰符

  考虑下面类首的声明:

  Java代码

static int intVal =上海徐汇企业网站设计与制作style="color: #000000;"> 42;
static String strVal = "Hello, world!";

  编译器生成一个类初始化方法clinit, 当类初次被使用时执行,这个方法将42存入intVal中,并得到类字符串常量strVal的引用。当这些值在后面被引用时,他们通过字段查找进行访问。

  我们改进实现,采用 final关键字:

  Java代码 

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

  类不再需要clinit方法,因为常量进入了dex文件中的静态字段初始化器中。引用intVal的代码,直接调用整形值42,而访问strVal时也会采用相对开销较小的 string constant(字符串常量)指令替代字段查找。(这种优化仅仅是针对基本数据类型和String类型常量的,而非任意的引用类型。但尽可能的将常量声明为static final类型是一种好的做法。

  8. 使用改进的For循环语法

  改进的for循环(有时被称为for-each循环)能够用于实现了iterable接口的集合类及数组中。在集合类中,迭代器促使接口访问hasNext()和next()方法,在ArrayList中,计数循环迭代要快3倍(无论有没有JIT),但其他集合类中,改进的for循环语法和迭代器具有相同的效率。

  这里有一些迭代数组的实现:

  Java代码 

 
 
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()不分上下。他采用了JDK1.5中的改进for循环语法。

  结论:优先采用改进的for循环,但在性能要求苛刻的ArrayList迭代中考虑采用手写计数循环。

  9. 在私有内部内中,考虑用包访问权限替代私有访问权限

  考虑下面的定义:

  Java代码

 
 
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( 上海闵行企业网站设计与制作yle="color: #000000;">" Value is " + value);
}
}

  需要注意的关键是:我们定义的一个私有内部类(Foo$Inner)直接访问外部类中的一个私有方法和私有变量。这是合法的,代码也会打印出预期的Value is 27。

  但问题是虚拟机认为从Foo$Inner中直接访问Foo的私有成员是非法的,因为他们是两个不同的类,尽管Java语言允许内部类访问外部类的私有成员,编译器生成几个综合方法来桥接这些间隙。

  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方法的地方调用这些静态方法。这意味着这些代码将直接存取成员变量归结为通过存取器方法访问。之前提到存取器访问如何比直接访问慢,这例子说明,某些语言约定导致了不可见的性能问题。

  如果你在高性能的Hotspot中使用这些代码,可以通过声明被内部类访问的字段和成员为包访问权限,而非私有。不幸的是这意味着这些字段会被其他处于同一个包中的类访问,因此在公共API中不宜采用。

  10. 合理利用浮点数

  通常的经验是,在Android设备中,浮点数会比整型慢两倍,在缺少FPU,或是JIT的G1以及有FPU和JIT的Nexus One中确实如此(两种设备间算数运算的绝对速度差大约是10倍).

  速度术语中,在现代硬件上,float和double之间并没有不同。更广泛的讲,double大约2倍大。在没有存储空间问题的桌面机器中,double的优先级高于float。

  但即使是整型,有些芯片拥有硬件乘法,却缺少除法。这种情况下,整型除法和求模运算是通过软件实现的,考虑下当你设计Hash表,或是做大量的算术。

  11. 了解并使用类库

  除了通常的那些有限选择类库代码而非自己的原因外,考虑到系统空闲时用手写的汇编程序来替代类库方法,这可能比JIT中能生成的最好的等效Java代码还要好。典型的例子就是String.indexOf,Dalvik用内部内联来替代。同样的,System.arraycopy方法比Nexus One中有JIT的自行编码循环快9倍.

  12. 合理利用本地方法

  本地方法并不是一定比Java高效,至少,Java和native之间过渡的关联是有消耗的。而JIT并不能越过这个界限进行优化。当你分配本地资源时(本地堆上的内存,文件说明符等),往往很难实时的回收这些资源。同时你也需要在各个结构中编译你的代码,而非依赖JIT。甚至可能需要针对相同的架构来编译出不同版本:针对ARM处理器的GI编译的本地代码,并不能充分利用Nexus One上的ARM,而针对Nexus One上ARM编译的本地代码不能在G1的ARM上运行。

  当存在有你想部署到Android上的本地代码库时,本地代码显得尤为有用,而非为了Java应用程序的提速。

  结语

  最后:通常权衡的,先确定存在问题,再进行优化。确认你知道当前的性能,否则无法衡量你进行尝试所得到的提升。

  这份文档中的每个主张都有基准测试作为支持。你可以在code.google.com的dalvik项目中找到基准测试的代码。

  基准测试是用Caliper Java微基准测试框架构建的。微基准测试很难走对,Caliper帮你完成了其中的困难工作。即使当你察觉某些情况的测试结果并非你所想象的那样(虚拟机总是在优化你的代码那)。我们强烈推荐你用Caliper来运行你自己的微基准测试。

  同时你也会发现Traceview对分析很有用,但必须了解,他目前是不支持JIT的,这可能导致那些在JIT上可以胜出的代码超时。特别重要的,当根据Taceview的数据作出更改后,确保代码在没有Traceview时,确实跑的快了.

目录
相关文章
|
27天前
|
存储 Shell Linux
快速上手基于 BaGet 的脚本自动化构建 .net 应用打包
本文介绍了如何使用脚本自动化构建 `.net` 应用的 `nuget` 包并推送到指定服务仓库。首先概述了 `BaGet`——一个开源、轻量级且高性能的 `NuGet` 服务器,支持多种存储后端及配置选项。接着详细描述了 `BaGet` 的安装、配置及使用方法,并提供了 `PowerShell` 和 `Bash` 脚本实例,用于自动化推送 `.nupkg` 文件。最后总结了 `BaGet` 的优势及其在实际部署中的便捷性。
60 10
|
16天前
|
数据采集 JSON API
.NET 3.5 中 HttpWebRequest 的核心用法及应用
【9月更文挑战第7天】在.NET 3.5环境下,HttpWebRequest 类是处理HTTP请求的一个核心组件,它封装了HTTP协议的细节,使得开发者可以方便地发送HTTP请求并接收响应。本文将详细介绍HttpWebRequest的核心用法及其实战应用。
53 6
|
2月前
|
Linux iOS开发 开发者
跨平台开发不再难:.NET Core如何让你的应用在Windows、Linux、macOS上自如游走?
【8月更文挑战第28天】本文提供了一份详尽的.NET跨平台开发指南,涵盖.NET Core简介、环境配置、项目结构、代码编写、依赖管理、构建与测试、部署及容器化等多个方面,帮助开发者掌握关键技术与最佳实践,充分利用.NET Core实现高效、便捷的跨平台应用开发与部署。
64 3
|
2月前
|
开发框架 监控 安全
.NET 应用程序安全背后究竟隐藏着多少秘密?从编码到部署全揭秘!
【8月更文挑战第28天】在数字化时代,.NET 应用程序的安全至关重要。从编码阶段到部署,需全面防护以保障系统稳定与用户数据安全。开发者应遵循安全编码规范,实施输入验证、权限管理和加密敏感信息等措施,并利用安全测试发现潜在漏洞。此外,部署时还需选择安全的服务器环境,配置 HTTPS 并实时监控应用状态,确保全方位防护。
38 3
|
2月前
|
缓存 Java API
【揭秘】.NET高手不愿透露的秘密:如何让应用瞬间提速?
【8月更文挑战第28天】本文通过对比的方式,介绍了针对 .NET 应用性能瓶颈的优化方法。以一个存在响应延迟和并发处理不足的 Web API 项目为例,从性能分析入手,探讨了使用结构体减少内存分配、异步编程提高吞吐量、EF Core 惰性加载减少数据库访问以及垃圾回收机制优化等多个方面,帮助开发者全面提升 .NET 应用的性能和稳定性。通过具体示例,展示了如何在不同场景下选择最佳实践,以实现更高效的应用体验。
31 3
|
2月前
|
前端开发 JavaScript 开发工具
跨域联姻:React.NET——.NET应用与React的完美融合,解锁前后端高效协作新姿势。
【8月更文挑战第28天】探索React.NET,这是将热门前端框架React与强大的.NET后端无缝集成的创新方案。React以其组件化和虚拟DOM技术著称,能构建高性能、可维护的用户界面;.NET则擅长企业级应用开发。React.NET作为桥梁,使.NET应用轻松采用React构建前端,并优化开发流程与性能。通过直接托管React组件,.NET应用简化了部署流程,同时支持服务器端渲染(SSR),提升首屏加载速度与SEO优化。
28 1
|
2月前
|
存储 缓存 安全
.NET 在金融行业的应用:高并发交易系统的构建与优化之路
【8月更文挑战第28天】在金融行业,交易系统需具备高并发处理、低延迟及高稳定性和安全性。利用.NET构建此类系统时,可采用异步编程提升并发能力,优化数据库访问以降低延迟,使用缓存减少数据库访问频率,借助分布式事务确保数据一致性,并加强安全性措施。通过综合优化,满足金融行业的严苛要求。
33 1
|
2月前
|
大数据 开发工具 开发者
从零到英雄:.NET核心技术带你踏上编程之旅,构建首个应用,开启你的数字世界探险!
【8月更文挑战第28天】本文带领读者从零开始,使用强大的.NET平台搭建首个控制台应用。无论你是新手还是希望扩展技能的开发者,都能通过本文逐步掌握.NET的核心技术。从环境搭建到创建项目,再到编写和运行代码,详细步骤助你轻松上手。通过计算两数之和的小项目,你不仅能快速入门,还能为未来开发更复杂的应用奠定基础。希望本文为你的.NET学习之旅开启新篇章!
29 1
|
2月前
|
数据库 C# 开发者
WPF开发者必读:揭秘ADO.NET与Entity Framework数据库交互秘籍,轻松实现企业级应用!
【8月更文挑战第31天】在现代软件开发中,WPF 与数据库的交互对于构建企业级应用至关重要。本文介绍了如何利用 ADO.NET 和 Entity Framework 在 WPF 应用中访问和操作数据库。ADO.NET 是 .NET Framework 中用于访问各类数据库(如 SQL Server、MySQL 等)的类库;Entity Framework 则是一种 ORM 框架,支持面向对象的数据操作。文章通过示例展示了如何在 WPF 应用中集成这两种技术,提高开发效率。
41 0
|
2月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
53 0
下一篇
无影云桌面