Android计算优化解析

本文已同步发表到个人技术微信公众号,扫一扫文章底部的二维码或在微信搜索 “程序员驿站”便可关注,天天都会更新优质技术文章。同时,也欢迎加入QQ技术群(群号:650306310)一块儿交流学习!程序员

这篇文章是继“Android电量优化全解析”、“Android内存优化全解析”、“Android渲染优化解析”以后关于Android性能优化的第四篇原创文章,主要讲解了Android计算优化相关知识点,但愿对你们有所帮助。今天我讲述的内容按照下面的结构进行。算法

低性能函数

让咱们首先从一个很是熟悉的问题开始,也就是函数执行过慢的问题,这是性能概念方面的初级基础计算机科学知识。有时候,当你写完代码以后,你发现它的运行速度,比你预想的要慢,这种状况常常会平白无故地出现,你专一于采用某种方法编写代码,来解决特定的问题,可是很快你发现代码的运行时间过长,远远超过你的预期。 代码运行时间过长的问题,在很大程度上能够归咎于编程语言,固然还有相关硬件执行代码的方式。例如,在一些老旧硬件上,执行浮点比较算法分支语句所花的时间,几乎是整数或布尔数值的四倍。其缘由在于芯片架构,负责浮点计算的CPU部分在分支逻辑阶段以后开始工做。这意味着任何浮点比较都须要等待,直到循环管道结束,从而拖延其余运算,直到分支逻辑最终执行完成,可是请不要感到惧怕。现代硬件一般不须要处理这种细微问题,可是这也说明了一个很重要的观点,即,你编写代码的方式会影响性能,具体视硬件上执行的编程语言而有所不一样。这个问题甚至能够追溯到芯片架构。我想要说的是,为了优化你的代码,你须要理解系统如何运行代码。数据库

缓慢的函数执行一般是因为两方面的问题形成的,第一种是执行速度很慢的函数,这种函数很容易被发现。你的某些函数所花费的时间,超过你的预期2倍、10倍,甚至50倍,这种问题容易解决。只要找到那些运行很慢的函数,查看代码找到问题所在,而后想办法解决就能够了。更难发现的是第二种类型,千方百计也难以发现,尤为是当你有数以千计的函数时,每一个函数所用的时间都额外增长一毫秒,从而致使整个程序执行速度变慢数百毫秒,这种类型的问题很难跟踪,并且更难以解决,由于一般你须要分析,每段执行代码才能发现这些小问题,这最终会影响你的产品发布,进而影响公司业绩。编程

要解决这些细小的问题,主要的方法是剖析(profiling),经过时间线分析方法,找到执行速度缓慢的代码部分,或者时间明显长于其它代码的部分,而后进行一些细小变动。而后再次进行时间线分析,找到执行很慢的函数以后,你须要对这些函数的代码行进行时间线分析,以及调用这些函数的全部其余函数。这项工做确实至关繁琐,除非你是这个领域的专家,可是不要惧怕,Android SDK有一些很不错的工具,帮助你找到这些有问题的代码部分,让你可以当即解决它们,让咱们来了解它们。数组

Traceview工具

将演示如何跟踪你的应用程序中的一些计算相关的性能问题。在这个演示中,咱们将使用工具来跟踪Sunshine应用程序,这个工具是TraceView。咱们载入它,打开DDMS视图选择咱们要分析的应用,请你注意一下,工具栏上的一些图标,尤为是这个图标。看上去像是三面箭头,上面有红色的圆点,若是按这些按钮,会出现一些提示,说将开始进行方法分析。这是TraceView的启动方法,咱们点击它。将出现一个弹出窗口,提示有两种方法来分析你的应用程序。你能够记录每一个方法的输入和输出,他们对资源的要求很高,或者,你也利用示例进行一些分析。其含义是,默认状况下分析程序,将会每1000毫秒侦测一次你的应用程序,以发现和记录实际上在运行的功能,如今,让咱们来使用这些默认设置。我点击一下OK,既然分析程序已经在继续,咱们就与你的应用程序进行交互,看可否记录一些动做。所以跳转到这里与Sunshine应用程序进行交互,好极了,山景地区的天气。不幸的是,周末的天气却不太好,将会下雨。咱们看看海岸区域,咱们在南加州的朋友还好吗?奇怪,他们已经进入冬季,这在圣地亚哥是不多见的。咱们回到Android Device Monitor,咱们想要中止分析。咱们仍是应该点击这个图标,启动时也是点击它。在最上方有一个黑色图标或者黑色方块,点击它能够中止分析。如今,可能须要一点时间来载入跟踪记录,将会显示在窗口上方,选项卡的这个位置。请记住,实际所用的时间可能更长一点,具体取决于实际记录的内容。缓存

咱们来看跟踪视图,跟踪视图有两个主要组成部分。上方窗格的名称是timeline面板,下方窗格内有不少的信息,称为profile面板。这个时间线可以很好的显示代码的执行状况,这里显示的每一行,实际上对应于一个线程。显示的每个颜色,对应于一个正在运行的特定方法。例如,咱们能够看到,主线程的全部活动,咱们能够看到方法启动和中止时间点,更有用的是放大这里,找到特定的方法,了解他们是如何执行的。它们会以这种U型模式显示出来。这里的条形表示,方法的启动时间。右侧的条形表示,方法的中止时间。条形的宽度表示方法执行所用的时间。如今,咱们选择一个特定的方法,咱们跳转到跟踪视图窗口的底部,这里,咱们看到一些分析数据显示出来。咱们能够看到哪些方法调用了咱们选定的方法。在这里,用他们的父级方法显示为蓝色,咱们还能够看到一些信息,显示在这个方法内调用了哪些方法。也就是说,咱们调用了一个发送输入事件方法。在选择以后,会显示一个本地条柱。对于咱们选择的全部这些方法,都有大量的附加统计信息。例如,能够看到独占CPU时间,咱们可使用这些信息,找到具体方法的特定问题。非独占CPU时间是特定方法在其内部调用的全部方法所用的时间。这能够帮你在信息树内找到,选定方法的特定问题。另外一个十分有用的信息是,方法被调用了多少次,或者递归调用自己多少次。若是咱们滚动到右侧,咱们能够找到这些信息,这里有一个列名为"calls and recursion",此列显示方法被调用多少次,或者它被递归调用多少次。在这个分析面板中,有大量的附加信息。另外,不要忘记这个搜索框,它能够帮助查找你所关心的功能。性能优化

  • 每一个方法前面都有一个数字,按照Incl CPU Time时间的排序序号
  • 展开一个方法后能够看到有两部分
    1. Parent表示调用这个方法的方法,能够叫作父方法
    2. Children表示这个方法中调用的其余方法,能够叫作子方法
  • Profile面板中各列做用说明:
列名 做用
Name 该进程运行过程当中所调用的函数名
Incl Cpu Time 函数占用的CPU时间,包含内部调用其它函数的CPU时间
Excl Cpu Time 函数占用的CPU时间,但不包含内部调用其它函数所占用的CPU时间
Incl Real Time 函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间
Excl Real Time 函数运行的真实时间(以毫秒为单位),不包含调用其它函数所占用的真实时间
Calls+Recur Calls/Total 函数被调用次数以及递归调用占总调用次数的百分比
Cpu Time/Call 函数调用CPU时间与调用次数的比(该函数平均执行时间)
Real Time/Call 同CPU Time/Call相似,只不过统计单位换成了真实时间

备注: CPU time就是CPU实际花了多少时间在运行函数,CPU在多进程环境下不会把全部时间用在一个进程上的服务器

批处理与缓存技术

我想向大家介绍我最喜欢的两个性能技术,批处理(batching)和缓存(caching)。前面咱们已经说过一些函数和运算,须要很是大的资源开销,这也会影响计算性能。例如,在执行以前把数据载入特定的内存区域,或者,在搜索以前对数值集进行排序,在执行屡次以后,并且次数确实是个很大的数字,资源开销将会严重影响应用程序的性能。批处理是能够帮助解决这种性能问题,它消除每一个运算的独立执行开销,好像是全部人都开一辆车,而不是每一个都开一辆,从而节省汽油。这种状况最多见于在执行运算以前,你须要准备数据。例如,在查找集合中的值时,最有效的方法是进行排序,而后进行二分法搜索等等。有一点必须弄清楚,这并非最有效的方法。这只是举一个例子而已,最简单的方法是写一个函数,提供一个集合和一个值,对集合进行排序,而后查看值是否存在于集合之中。对于某些性能要求来讲,这样作是能够的。可是,若是有10000个值,并且总共须要数百万组数据,排序所花费的时间,将会增长不少倍,答案很明确。对这组数据一次性完成排序,而后查找全部10000个值,并非明智的方法。这时就须要用到批处理,咱们找到重复的运算,找到以后,进行批处理。微信

缓存是与批处理类似的概念,这也是目前为止,你能理解的最重要的性能技术。这项技术全面地推进现代计算机科学的发展,以计算机为例,内存的做用是用来存储信息,让CPU可以更快的访问数据,其速度远快于访问硬盘数据。网络

或者以网络为例,世界各地存在大型服务器仓库,它们被称为数据中心,它们的做用是存储火缓冲被频繁访问的内容。这样,你的计算机就没必要每次都访问远在12000英里以外的服务器。你在埃及的朋友可能在这个服务器上发布了一张图片,固然,若是你在埃及,这样的缓冲服务器可能就没有什么意义,可是你已经明白其中的道理。以代码为例,最多见的缓存优化一般涉及屡次计算,可是若是始终相同的数据,例如,在循环计算中,你计算一个4x4数列的导数,结算始终是相同的,每次从新计算循环迭代,实际是在浪费计算机资源。相反,在循环流程的外部存储导数的结果,并让你的内部循环语句引用缓存结果,能够极大地提高效率。我之因此喜好缓存和批处理,是由于他们可以改善全部你可以想到的性能问题,包括咱们在本专题中提到的问题,这是两个很是有效的技术。若是你想成为一名性能专家,你最好可以熟练掌握这两项强大的技术。

使用批处理与缓存解决问题

目标: 在这个例子中,您将使用Traceview工具来查找并肯定哪些是阻碍应用程序性能问题的代码。而后,您将应用批处理或缓存来优化性能不佳的代码。

1)观察: 首先,让咱们看看咱们可以用怎样一个给定的应用功能来观察一个问题。在设备上启动compute应用,而后启动Batching/CachingActivity。你会看到一个正在舞蹈的海盗动画。

上面有一个写着"计算一些Fibonacci数"的按钮,点击它。你观察到了什么,为何会这样?不难看出,咱们的海盗朋友中止了摇摆。确定是按下按钮以后才致使了这个现象,如今,你已经观察到了一个问题,咱们来分析下。

2)Profile: 您刚刚了解了Traceview,让咱们把它运用起来。点击按钮,观察程序的运行轨迹。

3)分析: 根据运行轨迹分析,找出致使问题的方法是谁?

在这里,咱们要找的答案,其实是computeFibonacci方法。让咱们看看,我是怎么样找到它的。在下面的活动上运行TraceView,当我按下这个computeFibonacci函数,将会剖析这个函数。这是Traceview的输出,这是运行Traceview时看到的输出,你应该看到一些相似的内容,请注意这个大的粉色区域,这很糟,基本上,这表示有些函数在咱们的主线程上占用大量的CPU时间。若是你按照独占CPU时间排序或者将鼠标悬停在这个粉色区域。你会发现computeFibonacci方法,它来自于咱们的缓存活动,是占用CPU资源最多的函数,咱们须要解决这个问题。

解决斐波那契

有哪些好方法能够解决这个问题呢?请选择全部正确答案。咱们不该该为主线程增长任何没必要要的额外的工做,咱们应该让线程仅处理用户输入和屏幕绘制.咱们看看是否可以优化函数,使用共享技术提升运行速度和减小资源开销,让咱们缓存这些中间值。

让咱们来看看代码,看看发生了什么。咱们在主线程收到一个onClick()事件计算斐波那契。看看方法:

若是你熟悉算法理论,你能够看到,斐波那契数递归执行,从代码运行的角度来看,它是很是耗时的。解决这个问题的方法之一是经过计算和缓存中间结果。

主线程阻塞

为了确保应用程序的高性能,每项功能都应该尽量高效地运行。可是这些功能的执行时间以及它们在代码中所处的位置也很重要,当你首次启动一个Android应用程序时,朱执行线程就已经建立了,主线程很是重要,由于它负责运行你的代码,并在合适的视图位置发送事件和执行绘图功能。这些前面咱们已经讲过,基本上来讲,主线程是应用程序所在的线程,有时候,主线程也称为UI线程。例如,若是你触摸屏幕上的按钮,UI线程将会发送一个触摸事件给视图,视图将按钮状态设定为已按下设定,而后向事件队列发送一个有效请求,而后UI线程处理此请求,并通知按钮将其自己绘制为已按下状态。若是你有任何触摸事件的处理代码块,将会在线程中执行,这些触摸处理所用的时间越长,线程的执行时间就会越长,在绘图功能执行完以前,视图将会更新显示状态,让用户可以看到其状态,这里须要记住的是,输入处理代码与渲染和更新代码,共享这个线程的处理周期时间。

这意味着,在触摸事件处理,网络访问或数据库查询等计算周期时间,UI不会更新绘图,在简单的状况下,渲染周期可能会延误16毫秒左右,而让用户感到延迟。可是,若是你暂停UI线程渲染超过5秒,用户将会看到"应用程序未响应"对话框,并询问用户是否会想要关闭你的应用程序,这样可能致使用户中止使用。那你如何解决这个问题,你要找出不须要在主线程上执行的功能,也就是说,不须要等它们完成以后,才能执行绘图。你应该将这个功能转移到一个单独的独立线程,这个线程不会阻止UI线程。例如,若是你按一下提交按钮,以完成一个订单,而后编写和发送确认邮件,这能够在单独的线程上完成。Android有系列很好用的API,可以简化这些工做。

使用Traceview工具明确问题所在

我将要演示如何操做traceview,这个程序,能够识别全部安装程序的帧速率,操做方法以下,安装后,点击show on click handler按键,各位会看到很眼熟的跳舞海盗,而后点选display an image按钮,如今你们会看到海盗不动了,但若是再这样作,海盗又继续跳舞了,同时Android图示也会出如今海盗下方。跟以前同样,只要点击这个按钮,海盗就不动了,下面要怎样操做,想必你们都知道了,就是要利用这些工具,如今演示怎样操做traceview。 在这里,查看下刚才追踪的数据,结果出现冗长的数据,这是什么意思呢?检查数据,找出哪两个方法调用得最频繁。辨别哪些程序,占用了大量的CPU资源?

咱们来看一下Traceview输出,请注意这个大的活动区域,咱们来探讨一些观察到的信息,你会发现,最上方的这个函数,在排序时占用的资源最多。还有其余一些函数,例如这个nativePullOnce,可是它们是系统调用,咱们没法控制它们。若是咱们更进一步,就会注意到这个nativeSetPixel和这个nativeGetPixel。让咱们看看它们来自于哪里,咱们展开它,看到了!!在繁忙的UI线程活动的某个位置,setPixel被调用,它来自于咱们的应用代码,getPixel也是如此,彷佛来自于繁忙的UI线程活动。如今,咱们发现setPixel和getPixel,来自于繁忙的UI线程活动,让咱们来更深刻地探讨。

所以getPixel和setPixel并非咱们编写的代码,在这个代码中哪一个父级方法调用getPixel和setPixel?父级方法其实是sepiaAndDisplayImage,让咱们来看Traceview,来了解其含义。咱们回到跟踪视图,若是咱们展开可收缩菜单,咱们能够找到父级调用堆栈,看到setPixel实际上被sepiaAndDislayImage方法调用。它在咱们繁忙的UI线程活动以内,让咱们看看如何优化。

异步任务

对图像进行编码处理是一个至关大的任务,这也是咱们不能轻易地优化掉的任务。特别是那些涉及网络接入,冗长的数据库调用和图像处理,那么通常规则是将它们从主线程移除。 让咱们来使用异步任务:

容器性能

前面咱们讲过,一些类型的硬件可能会形成程序执行速度较慢,还记得那个浮点分支问题吗?对于今天的硬件来讲,这已经不是问题。可是有一些问题仍是须要引发注意,好比说,你所使用的编程语言的基本元素的效率,以排序等基本算法为例,如今,有不少的排序算法,对于不一样的状况,它们各有优劣,例如,当元素数量少于一千或在大型已排序列表中寻找一个对象时,快速排序法一般比起冒泡排序法更快。通常状况下,最好的方法是二分查找算法,可是,当在未排数组中寻找对象时状况变得彻底不一样,不一样于比较每个对象以查找你想要的值。你可使用一个哈希函数来当即找到它,这是现代计算机科学和数据结构方面的基本知识。

幸运的是,现代编程语言像Java等,为你提供了这些容器和算法,所以你再也不须要本身反复地编写Murmur3哈希函数和快速排序算法。可是你须要知道另一些事情,在我多年的编程生涯中,一个常常会影响项目性能的问题,是因为这些语言提供的容器对象的性能所引发的。这听起来难以想象!Java提供一个矢量类的实现,你能够任意push、pop,添加和取消对象,为了得到这种灵活性,它在内部使用链式列表结构,这种结构具备一系列独特的性能特性,在你操做这种列表时,它的速度超级快,可是,当你在其余位置进行插入或删除时,它会消耗大量的时间。我要说的是,底层系统提供的这些容器并不会考虑,你的程序将会如何实际使用它们,James Sutherland发表了一系列的基准测试报告,他认为,咱们须要注意性能与功能之间的一些差别。例如,他发现Hashtable比HashMap大约快22%,具体视你如何使用这些容器而有所不一样,咱们须要思考的是,你是否曾经分析过你在代码中使用的容器类。你是否坚信,你在代码中使用的容器的实际运行速度绝对是最快的。一个好消息是,你可使用Android中的MPI来剖析这些容器的性能。

数据结构

在这个例子中,你将会看到,建立应用时,容器中不恰当的数据结构所形成的性能问题,为此咱们可使用Android SDK中的工具,来识别不恰当数据结构带来的性能问题。存储与修改应用程序数据代码的性能问题,很容易被开发人员忽悠,因此让咱们来探讨这个问题,并从性能角度来解决。对于这个示例,咱们将重点关注方法的执行时间,这个方法生成一个数字列表,并按它们的受喜好程度排名,为了便于演示,当咱们按下这个按钮,也就是dump popular numbers to log按钮,将会调用这个方法,与前面的示例类似。这段代码会形成图像短暂停顿从而影响跳舞海盗图像的帧率。若是你看一下logcat,你会发现,它经过tag popularity dump运行,像这样。让咱们看看底层发生了什么,并学习如何测量这段代码的运行速度,咱们想要仔细弄清楚,当咱们点击这个按钮时发生了什么,所以咱们使用trace类beginsection和endsection方法来指定开始测量位置和结束位置。首先,让咱们找到计算受喜好程度的代码,咱们将使用它计量代码效率和运行时间。

问题: beginSection与endSection应该放在哪一个地方?

在这个例子中,你会看到咱们是如何选择一个更有效的数据结构,提升了数据访问时间。

问题: 经过使用dumpPopularRandomNumbersByRank(),咱们粗略招致排序操做的成本,再加上o经过双循环迭代来生成排名列表(N^ 2)成本。

改进注意事项: 总有一个项排序一次性成本(你会根据本身的数据集的大小挑选理想的排序项)。 经过使用一个HashMap中,咱们经过得到直查询时间(为O(n))与二次时间为O(n^2)未优化的状况下,阵列的访问节省时间。咱们节省的访问时间这一个订单,由于数据已经存储在键值对!

这显著事项当n(或样品尺寸)特别大,这多是若是你用,好比工做,在世界上全部的职业足球运动员名单,想碰到一些属性显示它们的排名状况。

让咱们花点时间确认的改善。来吧,在加回(若是你删除它)调用endSection和beginSection。而后,运行在优化代码systrace。

关注个人技术公众号"程序员驿站",天天都有优质技术文章推送,微信扫一扫下方二维码便可关注:

image
相关文章
相关标签/搜索