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

本文深入探讨了在Android应用开发中测量性能的重要性及其方法,包括使用System.currentTimeMillis()、System.nanoTime()等API进行时间测量,通过Debug.threadCpuTimeNanos()分析线程CPU时间,以及如何利用Android的logging机制进行高效调试。此外,文章还展示了如何在代码中实现实例,以确保优化措施的有效性和针对性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第六章 基准测量和分析

能够测量性能是有必要的,可以决定是否需要优化,优化实际提升了什么。
性能在大多数情况下是测量完成一个操作的所需时间。比如,游戏的性能经常用每秒多少帧可以被渲染来测量,直接依赖于需要多少时间去渲染帧:为了达到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); // 以秒为单位的间隔
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值