性能测试

Android O 中包含用于测试吞吐量和延迟的 binder 和 hwbinder 性能测试。虽然有很多场景都可用于检测可察觉的性能问题,但运行此类场景可能会比较耗时,而且相应结果通常要到集成完系统之后才可获得。借助 Android O 中提供的性能测试,您可更轻松地在开发过程中进行测试、及早发现严重问题以及改善用户体验。

性能测试包括以下四个类别:

  • binder 吞吐量(在 system/libhwbinder/vts/performance/Benchmark_binder.cpp 中提供)
  • binder 延迟(在 frameworks/native/libs/binder/tests/schd-dbg.cpp 中提供)
  • hwbinder 吞吐量(在 system/libhwbinder/vts/performance/Benchmark.cpp 中提供)
  • hwbinder 延迟(在 system/libhwbinder/vts/performance/Latency.cpp 中提供)

关于 binder 和 hwbinder

binder 和 hwbinder 都是 Android 进程间通信 (IPC) 基础架构,它们共用同一个 Linux 驱动程序,但在本质上具有以下不同之处:

方面 binder hwbinder
用途 为框架提供通用型 IPC 方案 与硬件通信
属性 专门针对 Android 框架使用情景做了优化 开销少,延迟低
更改前台/后台的调度策略 不会
传递参数 使用由 Parcel 对象支持的序列化 使用分散缓冲区,避免因复制 Parcel 序列化所需数据而产生开销
继承优先级 不会

binder 和 hwbinder 进程

Systrace 可视化工具会以如下方式显示事务:

图 1. binder 进程的 Systrace 可视化。

在上述示例中:

  • 四 (4) 个 schd-dbg 进程是客户端进程。
  • 四 (4) 个 binder 进程是服务器进程(名称以 Binder 开头,且以序列号结尾)。
  • 每个客户端进程始终都会与某个服务器进程(供其客户端专用)配对。
  • 所有客户端-服务器进程对均由内核同时单独调度。

在 CPU 1 中,操作系统内核会运行客户端以发出请求。然后,它会尽可能地使用同一 CPU 唤醒服务器进程、处理请求,并会在处理完请求后切换回原环境。

吞吐量和延迟

在理想的事务中,由于客户端进程和服务器进程可以无缝切换,吞吐量测试和延迟测试在生成的信息方面不会有很大差异。不过,如果操作系统内核在处理来自硬件的中断请求 (IRQ)、等待锁定,或只是选择不立即处理信息,则可能会形成延迟气泡。

图 2. 因吞吐量测试结果和延迟测试结果之间的差异而形成的延迟气泡。

吞吐量测试会生成很多具有不同有效负荷量的事务,因此可以很好地估算常规事务时间(在最理想的情况下)以及 binder 可达到的最大吞吐量。

相比之下,延迟测试不会对有效负荷执行任何操作,以最大限度地减少常规事务时间。我们可以利用事务时间来估算 binder 开销、对最坏的情况进行信息统计,并计算那些在延迟方面达到指定截止时间的事务所占的比例。

处理优先级倒置

如果优先级较高的线程在逻辑上需要等待优先级较低的线程,就会出现优先级倒置的问题。实时 (RT) 应用存在优先级倒置问题:

图 3. 实时应用中的优先级倒置。

如果某个线程使用 Linux 完全公平的调度程序 (CFS) 进行调度,则即使其他线程的优先级较高,该线程也总会有机会运行。因此,采用 CFS 调度的应用会将优先级倒置作为一种预期行为(而非问题)来处理。不过,如果 Android 框架需要使用 RT 调度,以保证优先级较高的线程的权限,则必须先解决优先级倒置问题。

binder 事务中的优先级倒置示例(RT 线程在等待 binder 线程提供服务时在逻辑上被其他 CFS 线程阻塞):

图 4. 优先级倒置;被阻塞的实时线程。

要避免出现阻塞情况,您可以在 binder 线程处理来自 RT 客户端的请求时,使用优先级继承暂时将 binder 线程升级到 RT 线程。请注意,RT 调度的资源有限,应谨慎使用。在具有 N 个 CPU 的系统中,当前 RT 线程的数量上限也为 N;如果所有 CPU 均已被其他 RT 线程占用,则额外的 RT 线程可能需要等待(因此将超出其截止时间)。

要解决所有可能出现的优先级倒置问题,您可以针对 binder 和 hwbinder 使用优先级继承。不过,由于 binder 广泛用于整个系统,因此为 binder 事务启用优先级继承可能会使系统中的 RT 线程数超过其所能处理的线程数。

运行吞吐量测试

吞吐量测试是针对 binder/hwbinder 事务吞吐量而运行的。在未过载的系统中,延迟气泡很少,而且只要迭代的次数足够多,就可以消除其影响。

  • binder 吞吐量测试位于 system/libhwbinder/vts/performance/Benchmark_binder.cpp 下。
  • hwbinder 吞吐量测试位于 system/libhwbinder/vts/performance/Benchmark.cpp 下。

测试结果

针对使用不同有效负荷量的事务的吞吐量测试结果示例:

Benchmark                      Time          CPU           Iterations
---------------------------------------------------------------------
BM_sendVec_binderize/4         70302 ns      32820 ns      21054
BM_sendVec_binderize/8         69974 ns      32700 ns      21296
BM_sendVec_binderize/16        70079 ns      32750 ns      21365
BM_sendVec_binderize/32        69907 ns      32686 ns      21310
BM_sendVec_binderize/64        70338 ns      32810 ns      21398
BM_sendVec_binderize/128       70012 ns      32768 ns      21377
BM_sendVec_binderize/256       69836 ns      32740 ns      21329
BM_sendVec_binderize/512       69986 ns      32830 ns      21296
BM_sendVec_binderize/1024      69714 ns      32757 ns      21319
BM_sendVec_binderize/2k        75002 ns      34520 ns      20305
BM_sendVec_binderize/4k        81955 ns      39116 ns      17895
BM_sendVec_binderize/8k        95316 ns      45710 ns      15350
BM_sendVec_binderize/16k      112751 ns      54417 ns      12679
BM_sendVec_binderize/32k      146642 ns      71339 ns       9901
BM_sendVec_binderize/64k      214796 ns     104665 ns       6495
  • 时间表示实时测量的往返延迟时间。
  • CPU 表示调度 CPU 以进行测试的累计时间。
  • 迭代表示执行测试函数的次数。

以 8 字节的有效负荷为例:

BM_sendVec_binderize/8         69974 ns      32700 ns      21296

… binder 可以达到的最大吞吐量的计算公式为:

8 字节有效负荷的最大吞吐量 = (8 * 21296)/69974 ~= 2.423 b/ns ~= 2.268 Gb/s

测试选项

要获得 .json 格式的结果,请使用 --benchmark_format=json 参数运行测试:

libhwbinder_benchmark --benchmark_format=json
{
  "context": {
    "date": "2017-05-17 08:32:47",
    "num_cpus": 4,
    "mhz_per_cpu": 19,
    "cpu_scaling_enabled": true,
    "library_build_type": "release"
  },
  "benchmarks": [
    {
      "name": "BM_sendVec_binderize/4",
      "iterations": 32342,
      "real_time": 47809,
      "cpu_time": 21906,
      "time_unit": "ns"
    },
   ….
}

运行延迟测试

延迟测试可测量以下事项所花费的时间:客户端开始初始化事务、切换到服务器进程进行处理,以及接收结果。此外,该测试还会查找可对事务延迟产生负面影响的已知不良调度程序行为,例如,调度程序不支持优先级继承或不接受同步标记。

  • binder 延迟测试位于 frameworks/native/libs/binder/tests/schd-dbg.cpp 下。
  • hwbinder 延迟测试位于 system/libhwbinder/vts/performance/Latency.cpp 下。

测试结果

测试结果(.json 格式)将显示有关平均/最佳/最差延迟以及超出截止时间的次数的统计信息。

测试选项

延迟测试采用以下选项:

命令 说明
-i value 指定迭代次数。
-pair value 指定进程对的数量。
-deadline_us 2500 指定截止时间(以微秒为单位)。
-v 获取详细的(调试)输出。
-trace 在达到截止时间时暂停跟踪。

以下几个部分会详细介绍每个选项,说明相关使用情况,并提供示例结果。

指定迭代

具有大量迭代次数并停用了详细输出功能的结果示例:

libhwbinder_latency -i 5000 -pair 3
{
"cfg":{"pair":3,"iterations":5000,"deadline_us":2500},
"P0":{"SYNC":"GOOD","S":9352,"I":10000,"R":0.9352,
  "other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996},
  "fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
},
"P1":{"SYNC":"GOOD","S":9334,"I":10000,"R":0.9334,
  "other_ms":{ "avg":0.19, "wst":2.9 , "bst":0.055, "miss":2, "meetR":0.9996},
  "fifo_ms": { "avg":0.16, "wst":3.1 , "bst":0.066, "miss":1, "meetR":0.9998}
},
"P2":{"SYNC":"GOOD","S":9369,"I":10000,"R":0.9369,
  "other_ms":{ "avg":0.19, "wst":4.8 , "bst":0.055, "miss":6, "meetR":0.9988},
  "fifo_ms": { "avg":0.15, "wst":1.8 , "bst":0.067, "miss":0, "meetR":1}
},
"inheritance": "PASS"
}

这些测试结果会显示以下信息:

"pair":3
创建一个客户端和服务器对。
"iterations": 5000
包括 5000 次迭代。
"deadline_us":2500
截止时间为 2500 微秒(2.5 毫秒);大多数事务都应达到该值。
"I": 10000
单次测试迭代包括两 (2) 项事务:
  • 一项按照正常优先级 (CFS other) 处理的事务
  • 一项按照实时优先级 (RT-fifo) 处理的事务
5000 次迭代相当于共计 10000 项事务。
"S": 9352
9352 项事务会在同一个 CPU 中进行同步。
"R": 0.9352
表示客户端和服务器在同一个 CPU 中一起同步的比例。
"other_ms":{ "avg":0.2 , "wst":2.8 , "bst":0.053, "miss":2, "meetR":0.9996}
由正常优先级调用程序分发的所有事务的平均 (avg)、最差 (wst) 和最佳 (bst) 情况。两个事务 miss 截止时间,使得达标率为 (meetR) 0.9996。
"fifo_ms": { "avg":0.16, "wst":1.5 , "bst":0.067, "miss":0, "meetR":1}
类似于 other_ms,但适用于由具有 rt_fifo 优先级的客户端分发的事务。fifo_ms 的结果很可能(但不需要)优于 other_ms,且 avgwst 值较低,而 meetR 则较高(如果考虑后台中的负荷,其差异可能会更大)。

注意:后台负荷可能会影响延迟测试中的吞吐量结果和 other_ms 元组。只要后台负荷的优先级低于 RT-fifo,就可能只有 fifo_ms 会显示类似的结果。

指定对值

每个客户端进程都会与其专用的服务器进程配对,且每一对都可能会独立调度到任何 CPU。不过,只要同步标记是 honor,事务期间应该就不会出现 CPU 迁移的情况。

确保系统没有过载!虽然过载系统中延迟较高是正常现象,但是针对过载系统的测试结果并不能提供有用的信息。要测试压力较高的系统,请使用 -pair #cpu-1(或谨慎使用 -pair #cpu)。使用 -pair nn > #cpu 进行测试会使系统过载,并生成无用信息。

指定截止时间值

经过大量用户场景测试(在合格产品上运行延迟测试),我们决定将 2.5 毫秒定为需要满足的截止时间要求。对于具有更高要求的新应用(如每秒 1000 张照片),此截止时间值将发生变化。

指定详细输出

使用 -v 选项显示详细输出。示例:

libhwbinder_latency -i 1 -v

-------------------------------------------------- service pid: 8674 tid: 8674 cpu: 1 SCHED_OTHER 0
-------------------------------------------------- main pid: 8673 tid: 8673 cpu: 1 -------------------------------------------------- client pid: 8677 tid: 8677 cpu: 0 SCHED_OTHER 0
-------------------------------------------------- fifo-caller pid: 8677 tid: 8678 cpu: 0 SCHED_FIFO 99 -------------------------------------------------- hwbinder pid: 8674 tid: 8676 cpu: 0 ??? 99
-------------------------------------------------- other-caller pid: 8677 tid: 8677 cpu: 0 SCHED_OTHER 0 -------------------------------------------------- hwbinder pid: 8674 tid: 8676 cpu: 0 SCHED_OTHER 0
  • 服务线程使用 SCHED_OTHER 优先级创建,且与 pid 8674 一起在 CPU:1 中运行。
  • 随后,第一个事务fifo-caller 启动。为处理该事务,hwbinder 会将服务器 (pid: 8674 tid: 8676) 的优先级升级到 99,并使用瞬态调度类别(打印为 ???)对其进行标记。接下来,调度程序会将服务器进程置于 CPU:0 中,以运行该进程并将它与其客户端使用的同一 CPU 进行同步。
  • 第二个事务调用程序的优先级为 SCHED_OTHER。服务器自行降级并为优先级为 SCHED_OTHER 的调用程序提供服务。

使用跟踪记录进行调试

您可以指定 -trace 选项来调试延迟问题。使用该选项时,延迟测试会在检测到不良延迟时停止跟踪日志记录。示例:

atrace --async_start -b 8000 -c sched idle workq binder_driver sync freq
libhwbinder_latency -deadline_us 50000 -trace -i 50000 -pair 3
deadline triggered: halt ∓ stop trace
log:/sys/kernel/debug/tracing/trace

以下组件可能会影响延迟:

  • Android 编译模式。Eng 模式通常比 Userdebug 模式速度慢。
  • 框架。框架服务如何使用 ioctl 来配置 binder?
  • binder 驱动程序。驱动程序是否支持精细锁定?驱动程序是否包含所有性能调整补丁程序?
  • 内核版本。内核的实时性能越好,结果就越好。
  • 内核配置。内核配置是否包含 DEBUG_PREEMPTDEBUG_SPIN_LOCKDEBUG 配置?
  • 内核调度程序。内核中是否具有 Energy-Aware 调度程序 (EAS) 或异构多处理 (HMP) 调度程序?有没有内核驱动程序(cpu-freq 驱动程序、cpu-idle 驱动程序、cpu-hotplug 等)影响调度程序?