用途
hz-simpleperf-debug 用于在 Meta VR 设备上获取硬件级 CPU 性能洞察。它适合以下场景:
- 判断应用是 CPU 密集型、内存密集型还是 I/O 密集型
- 找出消耗最多 CPU 周期的热点函数
- 按线程测量内核态与用户态的 CPU 开销
- 识别缓存抖动或分支预测问题
- 用硬件 PMU 计数器数据补充 Perfetto 追踪分析
该技能与 hz-perfetto-debug 互补:Perfetto 展示应用随时间的行为,simpleperf 展示 CPU 在硬件周期上的花费,包括 Perfetto 看不到的缓存未命中、分支预测失败和指令吞吐。
VR 性能背景
Meta VR 设备运行在移动 ARM SoC 上,有严格的热和功耗预算。CPU 密集型应用在以下刷新率下会掉帧:
| 刷新率 | CPU 帧预算 | 说明 |
|---|---|---|
| 120 Hz | 8.3 ms | 紧张,simpleperf 对找热点至关重要 |
| 90 Hz | 11.1 ms | 大多数应用的默认目标 |
| 72 Hz | 13.9 ms | 较重应用的降级选项 |
环境准备
Simpleperf 分析由 metavr CLI 提供支持。可将独立二进制安装到 PATH(参见 metavr-cli 技能),或通过 npx 免安装调用:
metavr --version
下文示例使用裸 metavr 命令;若使用 npm 分发版,请加前缀 npx -y。通过 USB 连接已开启开发者模式的 Meta VR 设备。
快速开始工作流
1. 分类工作负载
优化前先确定瓶颈类型:
# 分类前台应用的工作负载(10 秒采样)
metavr perf simpleperf classify
# 指定应用
metavr perf simpleperf classify --app com.example.myapp
# 自定义时长
metavr perf simpleperf classify --duration 15
返回带证据的分类结果:
| 分类 | 指标 | 优化策略 |
|---|---|---|
| CPU 密集型 | 高 IPC、低停顿比 | 优化算法、减少绘制调用、批处理工作 |
| 内存密集型 | 高停顿比(stalled-cycles-backend / cpu-cycles) | 减少缓存未命中、改善数据局部性、缩小工作集 |
| I/O 密集型 | 高每秒上下文切换 | 减少阻塞 I/O、使用异步、减少线程争用 |
2. 录制 CPU 热点
捕获 CPU 周期剖析以找出最昂贵的函数:
# 录制前台应用的 CPU 热点
metavr perf simpleperf record
# 自定义频率和时长
metavr perf simpleperf record --frequency 4000 --duration 10
# 指定应用
metavr perf simpleperf record --app com.example.myapp
录制以指定频率(默认 4000 Hz)采样 CPU 周期,生成显示哪些函数消耗最多 CPU 时间的剖析。
3. 测量内核开销
确定每个线程在内核态与用户态花费的 CPU 时间:
# 测量前台应用的内核开销
metavr perf simpleperf kernel-overhead
# 自定义时长
metavr perf simpleperf kernel-overhead --app com.example.myapp --duration 10
返回每线程的用户态与内核态 CPU 周期细分。某线程内核开销高(>20%)可能意味着:
- 过多系统调用(文件 I/O、内存分配)
- 驱动开销(GPU 命令提交、传感器访问)
- 内核同步原语中的锁争用
分析工作流
第 1 步:先分类
始终从分类开始,避免浪费时间优化错误的方向。
metavr perf simpleperf classify --app com.example.myapp --duration 10
基于结果的决策树:
- CPU 密集型 → 录制热点(第 2a 步),查看顶部函数
- 内存密集型 → 用缓存未命中事件录制,检查数据访问模式
- I/O 密集型 → 检查内核开销,在 Perfetto 中查看线程争用
第 2a 步:CPU 密集型应用——找热点
metavr perf simpleperf record --app com.example.myapp --duration 10
按 CPU 周期消耗查看顶部函数。常见 VR 热点:
| 函数模式 | 可能原因 | 修复 |
|---|---|---|
| Physics.* / PhysX | 复杂物理模拟 | 减少碰撞体数量、简化网格、增大固定时间步 |
| Render* / Draw* | 绘制调用过多 | 批处理材质、使用 GPU 实例化、减少唯一材质 |
| GC_* / gc_alloc | 垃圾回收压力 | 池化分配、避免每帧分配 |
| memcpy / memmove | 大数据拷贝 | 使用引用、减小缓冲区、避免不必要拷贝 |
| LZ4_* / compress | 资源解压 | 预解压、使用更轻压缩、缓存结果 |
第 2b 步:内存密集型应用——检查缓存行为
若分类显示内存密集型,问题可能是缓存未命中或内存带宽:
- 大工作集抖动 L1/L2 缓存
- 随机访问模式使预取器失效
- 线程间相邻缓存行的伪共享
使用 Perfetto hz-perfetto-debug 将内存密集区域与具体代码路径关联。
第 3 步:测量内核开销
metavr perf simpleperf kernel-overhead --app com.example.myapp
按线程解读结果:
| 线程 | 预期内核占比 | 高内核占比意味着 |
|---|---|---|
| 主/游戏线程 | < 5% | 过多文件 I/O、日志或分配 |
| 渲染线程 | 5-15% | 正常(GPU 驱动开销)。>20% = 驱动问题 |
| 工作线程 | < 5% | 线程同步开销 |
| 音频线程 | < 10% | 音频 HAL 调用的正常值 |
第 4 步:与 Perfetto 结合
Simpleperf 告诉你周期去了哪里,Perfetto 告诉你何时、在什么上下文中。配合使用:
- Simpleperf 分类揭示瓶颈类型
- Simpleperf 热点录制识别昂贵函数
- Perfetto 追踪(metavr perf capture)显示这些函数相对帧边界何时运行
- 使用 metavr perf query 将函数时序与掉帧关联
常见陷阱
- 不要在热节流时分析。录制前让设备冷却——被节流的时钟会扭曲周期计数。先用 metavr device info 检查热状态。
- 采样时长很重要。短录制(<5 秒)可能无法捕获代表性行为。分类至少用 10 秒。
- simpleperf 需要 shell 访问。若 adb shell simpleperf 失败,确保已开启开发者模式并授权 USB 调试。
- 频率与精度的权衡。更高采样频率(>8000 Hz)可能扰动移动 SoC 上的工作负载。默认 4000 Hz 是良好平衡。
- 分类是快照。应用可能在游戏过程中是 CPU 密集型,在场景加载时是 I/O 密集型。针对你正在优化的具体场景进行分析。
参考
特定主题的详细指南:
- 工作负载分类——PMU 计数器解读与瓶颈识别
- CPU 热点分析——录制和分析 CPU 周期剖析
- 内核开销——测量和减少内核态 CPU 使用