精通安卓性能优化-第六章(一)

来源:互联网 发布:欧文生涯数据统计 编辑:程序博客网 时间:2024/05/16 07:27

第六章 基准测量和分析

能够测量性能是有必要的,可以决定是否需要优化,优化实际提升了什么。
性能在大多数情况下是测量完成一个操作的所需时间。比如,游戏的性能经常用每秒多少帧可以被渲染来测量,直接依赖于需要多少时间去渲染帧:为了达到60帧每秒的帧率,每个帧渲染和显示的时间需要少于16.67毫秒。同样的,像第一章讨论的那样,100毫秒的响应时间通常是为了立即显示出效果。
在本章将学习在你的应用中不同的测量方式。你同样学习如何使用Traceview分析工具,跟踪Java代码和native代码并简单的辨识应用的瓶颈。最终,学习Android的logging机制,和如何利用过滤能力。

测量时间

当需要优化代码的时候,一个操作或者一系列的操作完成需要多少时间是信息的关键部分。不知道做某件事情花费多少时间,你的优化没办法测量。Java和Android提供了如下简单API,可以用来测量时间和性能:
(1) System.currentTimeMillis
(2) System.nanoTime
(3) Debug.threadCpuTimeNanos
(4) SystemClock.currentThreadTimeMillis
(5) SystemClock.elapsedRealtime
(6) SystemClock.uptimeMillis
通常,你的应用需要调用两次这样的方法,因为单次调用是没有意义的。为了测量时间,你的应用需要一个起始时间和一个结束时间,性能通过两次值的差测量。冒着冠冕堂皇的给人傲慢感觉的风险,现在是一个很好的时间讲有每秒1,000,000,000纳秒,或者说,纳秒是一秒的百万分之一。

NOTE:尽管有些方法以纳秒为单位的返回,并不表示是纳秒精度。实际的精度依赖于平台,设备不同结果不同。相似的,System.currentTimeMillis()返回毫秒的数量但是并不保证毫秒精度。

Listing 6-1 给出了一个通常的应用。

Listing 6-1 测量时间

long startTime = System.nanoTime();// 这里执行你期望测量的操作long duration = System.nanoTime() - startTime;System.out.println("Duration: " + duration);

一个重要的细节是Listing 6-1没有使用任何Android独有的。作为事实,测量代码仅使用java.lang.System, java.lang.String和java.io.PrintStream包。结果,你可以在另外一个Java应用使用相似的代码,而不必在Android设备上。Debug和SystemClock类,换句话说,是Android独有的。
尽管System.currentTimeMillis()作为一个测量时间的方法,实际上并不推荐使用,有两个原因:
(1) 它的精度不足够
(2) 改变系统时间会影响结果
代替的,你的应用可以使用System.nanoTime(),因为它提供了更好的精度。

System.nanoTime()

因为没有定义基准时间,你应该只使用System.nanoTime()测量时间间隔,如Listing 6-1所示。为了获取时间(作为时钟),使用System.currentTimeMillis(),因为它定义了返回值为从1970,1月1日,00:00:00 UTC开始的毫秒数。
Listing 6-2给你展示如何用System.nanoTime()测量时间。

Listing 6-2 System.nanoTime()

private void measureNanoTime() {    final int ITERATIONS = 100000;    long total = 0;    long min = Long.MAX_VALUE;    long max = Long.MIN_VALUE;    for (int i=0; i<ITERATIONS; i++) {        long startTime = System.nanoTime();        long time = System.nanoTime() - startTime;        total += time;        if (time < min) {            min = time;        }        if (time > max) {            max = time;        }    }    Log.i(TAG, "Average time: " + ((float)total / ITERATIONS) + " nanoseconds");    Log.i(TAG, " Minimum: " + min);    Log.i(TAG, " Maximum: " + max);}

在三星Galaxy Tab 10.1,平均时间大约是750纳秒。

NOTE:调用System.nanoTime()需要多少时间,依赖于实现和设备。

因为调度器最终有责任调度线程在处理单元的运行,你希望测量的操作可能会被中断,很可能会有几次,为另外一个线程留下空间。因此,你的测量结果可能包含花费在其他代码的时间,使得测试不正确,会被误导。
为了了解你的代码需要多少时间完成,你可以使用Android独有的Debug.threadCpuTimeNanos()方法。

Debug.threadCpuTimeNanos()

因为它只是测试花费在当前线程的时间,Debug.threadCpuTimeNanos()给你一个理想的概念自己的代码花费了多少时间去完成。然而,如果你要测量的执行在多个线程,单独的一个Debug.threadCpuTimeNanos()不会给你精确的估计,你需要在你所有的感兴趣的线程调用这个方法,并且把结果相加。
Listing 6-3给出了Debug.threadCpuTimeNanos()如何使用的一个简单的实例。它的使用和System.nanoTime()没什么不同,仅可以被用来测量一个时间周期。

Listing 6-3 使用Debug.threadCpuTimeNanos()

long startTime = Debug.threadCpuTimeNanos();// 警告:如果系统不支持这个操作可能会返回-1// 简单的睡眠1秒(在这个时间内其他线程会被调度执行)try {    TimeUnit.SECONDS.sleep(1);    // 和Thread.sleep(1000);} catch (InterruptedException e) {    e.printStackTrace();}long duration = Debug.threadCpuTimeNanos() - startTime;Log.i(TAG, "Duration: " + duration + " nanoseconds");

因为TimeUnit.SECONDS.sleep()的调用,代码需要大约1秒去完成,实际的执行代码花费的时间很少。实际上,在Galaxy Tab 10.1上运行代码大约是74毫秒。这是预期的,因为在两个Debug.threadCpuTimeNanos()之间除了将线程sleep 1秒没有做其他的事情。

NOTE: 参考TimeUnit类文档。TimeUnit提供了在不同的时间单元之间转换的方便方法,同样会执行线程相关的操作,比如Thread.join()和Object.wait()。

当然,你同样可以在应用的C代码使用标准的C时间函数去测量时间,如Listing 6-4所示。

Listing 6-4 使用C时间函数

#include <time.h>void foo() {    double duration;    time_t time = time(NULL);    // 在这里做些你想去测量的事情    duration = difftime(time(NULL), time); // 以秒为单位的间隔}


0 0