Skip to content

Binder性能优化

基于 Binder payload、调用次数、线程池、oneway 队列、allocator 和基准测试建立可验证的性能优化方法。

基于android-17.0.0_r1
AndroidBinder性能调试

Binder性能优化 ​

性能优化必须先明确成本发生在哪一层,再选择能把成本转移到可接受 owner 的改动。下面的决策图把 benchmark、trace 和回归指标放在优化动作之前。

本文承接 Binder one-copy、Binder线程耗尽排查、Binder大数据传输 和 Binder死锁排查。优化目标不是让一次空 transact 更快,而是减少整个业务调用图的序列化、调度、等待、锁和内存成本。每项改动都应由 payload 分布、transaction trace 和 benchmark 反向验证。

1. 先量调用图 ​

源码文件:frameworks/native/libs/binder/tests/binderRpcBenchmark.cpp

基准注释指出,系统中绝大多数 AIDL/HIDL transaction 小于一页;当单次 transaction 已很快,整体性能由多少次 Binder 调用协作、调度器、核心选择和热限制主导。优化前先统计每个业务操作的 transaction 数、同步深度、payload 和 P50/P95/P99,而不是只测一个 API。

2. 减少往返 ​

多个“查询一个字段”的同步 RPC 会重复付出 Parcel 编解码、驱动入队、线程唤醒和 reply 等待。可以设计一次返回稳定快照或服务端批处理,但批量结果必须有分页/数量上限;否则减少调用次数的同时可能触发 TransactionTooLargeException 和更长锁持有。

批量的判据是“减少依赖往返并保持有界 payload”,不是把任意列表塞进一个 Bundle。

3. 控制Payload ​

源码文件:frameworks/native/libs/binder/Constants.h、BpBinder.cpp、Binder.cpp

kLogTransactionsOverBytes = 300 * 1024。BpBinder、BBinder 对大请求和大 reply 记录 descriptor、code 和 flags,提供现成的热点入口。超过阈值的日志不是硬错误,但应检查是否能改成 ID、分页、fd 或共享 blob。

4. 选择共享内存 ​

小数据内联 Parcel 成本最低;Parcel::writeBlob 超过 16 KiB 且允许 fd 时转 Ashmem;长期复用或随机访问的大数据使用 memfd/MemoryHeapBase/AIDL fd 协议。共享内存减少 transaction payload,但增加 fd translation、mmap、页生命周期和同步协议,不能对所有小消息默认使用。

5. 同步与oneway ​

同步调用提供 reply 和错误,但占用调用线程直到完成;oneway 降低调用方等待,却在同一 Binder node 的 async_todo 串行排队并消耗 free_async_space。高频通知改 oneway 前应确认允许丢失/延迟、设置背压和合并策略,否则只是把延迟从客户端移动到服务队列。

6. 线程池配置 ​

BINDER_SET_MAX_THREADS 控制驱动懒启动 worker,不包含 startThreadPool/joinThreadPool 额外线程。线程数过少导致 pending 和 starvation,过多则增加并发内存、锁竞争和调度开销。应根据服务中同步阻塞比例、下游调用和 CPU 工作量压测,而不是统一设成最大值。

7. 缩短锁区 ​

服务端 onTransact 应在锁内读取/更新必要状态,锁外执行慢 I/O 和下游 Binder;否则所有 worker 会阻塞在同一 mutex。驱动自身也把页安装、buffer 清零等工作移出关键 mutex,说明减少临界区比单纯扩容线程更可靠。

8. Allocator压力 ​

频繁大 Parcel 会争用目标 binder_alloc::mutex、安装页并产生碎片;oneway 还消耗一半 buffer 的 async quota。观察 binder_transaction_alloc_buf/release trace、largest free block 和 free async space。若 allocator 是热点,减少并发在途数据、复用共享 fd 或拆分生命周期,而非增加线程。

9. 对象与fd ​

每个 Binder object、fd、offset 都需要驱动遍历、引用/文件转换和 SELinux transfer 检查。用大量小 Binder token 或 fd 代替 byte payload 也可能变慢。协议应传最少的稳定引用,并缓存已经获得的服务代理;缓存必须配合 death recipient 和重连,不能长期使用死亡 BpBinder。

10. 敏感清零 ​

TF_CLEAR_BUF 会让 allocator 在释放前清零 buffer。源码特意把大 buffer 清零移到 alloc mutex 外以降低竞争,但清零成本仍存在。只对敏感数据启用,不能为了“统一安全”在所有高频大事务上无差别使用;安全要求优先,性能优化只能在满足数据分类后进行。

11. 基准验证 ​

  • frameworks/native/libs/binder/tests/binderRpcBenchmark.cpp
  • frameworks/native/libs/binder/tests/binderThroughputTest.cpp
  • frameworks/native/libs/binder/tests/binderParcelBenchmark.cpp

现有 benchmark 覆盖 ping、两页字符串、64~65537 字节 throughput、Parcel vector 编解码和多 worker 并发。修改协议后应加入与真实 payload/并发相近的 case,同时用 Perfetto/Binder trace 验证调度和队列,而不是只比较 microbenchmark 均值。

12. 优化顺序 ​

  1. 删除不必要的同步往返和重复查询。
  2. 限制 payload、对象和 fd 数量。
  3. 选择内联、Blob 或显式共享内存。
  4. 缩短服务锁区和下游同步链。
  5. 为 oneway 建立背压/合并。
  6. 最后根据压测调整线程池。
  7. 用 P95/P99、CPU、allocator、ready/pending 和失败率复测。

13. 反优化边界 ​

大批量可能造成超大 reply;oneway 可能造成队列延迟和不可见错误;缓存可能返回陈旧状态或死亡代理;线程扩容可能放大锁和内存压力;共享内存可能增加同步复杂度。任何“优化”若没有明确 owner、消费者和失败恢复,都只是成本迁移。

14. 阅读检查 ​

给定一个每帧调用 20 次、每次 2 KiB 的同步接口,分别评估批量、oneway、缓存、共享内存和线程扩容。为每个方案指出它减少的源码成本、新增的队列/生命周期风险,以及应使用哪个 benchmark/trace 指标验证。