使用simpleperf分析RenderEngine耗时

  1. 问题背景

在M12上反馈一些视频播放转屏动画卡顿的问题, 主要包括优酷, B站转屏动画卡顿, 基本是必现问题. 必现问题就比较好办, 通过trace发现卡顿原因, SF中的RenderEngine线程running耗时, 有几个acticon:

  • 看看running在哪个CPU簇上, CPU频率如何
  • 能否通过提频改善
  • 友商机器情况如何
  • 小米其它机器情况如何

通过以上action确认, RenderEngine的摆核和CPU频率均与友商机器相当, 但友商耗时只有1.多ms, 相差十几倍. 其次通过定频方式将中核心CPU簇频率拉满, 仍然耗时10多ms超出一帧. 对比M11&M1高通机型, 也发现此问题, 看来是小米通用的问题.

通过M12的native包确认native版本没有此问题, 同时MTK的demophone也没有此问题, 确定是MIUI引入, 那么下步即要确定是谁引入的. 需要确认的问题就只有一个了:那就是找到耗时方法SkiaGL::drawLayers 里面小米的流程和demophone的差异点.

  1. simpleperf工具

Simpleperf 是Google随NDK一起发布的一款profile工具(注:从NDK r13开始),它是针对Android平台的一个 native 层性能分析工具。火焰图是基于 perf 命令结果产生的图片,用于展示 CPU 的调用栈。其中,纵轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。 横轴表示抽样数,如果一个函数在横轴占据的宽度越宽,就表示它被抽到的次数越多,即:执行的时间越长**(注意:横轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的)**。火焰图就是看顶层的哪个函数占据的宽度最大。只要有”平顶”(plateaus),就表示该函数可能存在性能问题。

  1. 脚本获取

  1. 开始抓取log

  2. 将上一步中获取到的simpleperf push 到手机mkdir /data/local/tmp中,并修改权限:

adb shell mkdir /data/local/tmp

adb push simpleperf /data/local/tmp/

adb shell chmod 777 /data/local/tmp/simpleperf

  1. 开始抓取火焰图

adb root

adb shell /data/local/tmp/simpleperf record -t xxx —duration 30 -o /data/local/tmp/perf.data —call-graph fp

  • —duration 10,录10sec的意思
  • -a 录制所有process的意思 || -p xxx 表示您期望录制哪个具体的process || -t 表示哪个thread
  • -o /data/local/tmp/csdj_xxxx_y_fpsgo_on.data 表示录制的profiling data保存哪里以及录制的filename
  • -e instructions,表示录制的event为指令
  • record 表示为录制
  • —call-graph dwarf 用在32位系统中,64位则采用**—call-graph** fp
  • ps –A | grep XXX 可以获取到XXX的tid和pid
  1. 解析火焰图

adb shell /data/local/tmp/simpleperf report -i /data/local/tmp/perf.data -o /data/local/tmp/perf_report.txt

or adb shell “/data/local/tmp/simpleperf report-sample —show-callchain -o /data/local/tmp/perf_report.txt” 可以生成柱状图

在本地终端执行git clone https://github.com/brendangregg/FlameGraph.git下载FlameGraph工具,下载完成如下

adb pull /data/local/tmp/perf.data

adb pull /data/local/tmp/perf_report.txt

python3.9 report_sample.py >out.perf //注意python版本

chmod 777 FlameGraph/stackcollapse-perf.pl

chmod 777 FlameGraph/flamegraph.pl

执行./FlameGraph/stackcollapse-perf.pl out.perf > out.folded

执行./FlameGraph/flamegraph.pl out.folded >out.svg,out.svg就是最后得到的火焰图,用浏览器打开就可以看

抓取/解析步骤比较繁琐, 自己可以写一个脚本, 也比较方便.

  1. RenderEngine耗时分析

附件:

http://minio.898311.xyz:8900/blogimg/17492845853107.svg

http://minio.898311.xyz:8900/blogimg/17492845853107.svg

要想找到skia流程中的差异, 如果自己对这块代码流程不熟悉, 通过加trace的方法一层层往下加的话需要耗费大量的debug时间, 显然不太可取, 那么simpleperf的火焰图就派上用场了.

RenderEngine是SF中的子线程, 属于native线程, 这样我们可以通过抓取M12和demophone(或者M12的native版本)的火焰图对比就能找出主要的流程差异了, 完美解决此问题.

分析步骤

  1. 依据火焰图的特性, 我们关注横轴占比最大的函数, 一路查看M12的火焰图找到android::renderengine::skia::SkiaGLRenderEngine::drawLayersInternal 这个函数横占比最大

  1. 查看代码发现drawLayersInternal方法即对应到trace中的”SkiaGL::drawLayers”, 说明我们找到了正确的位置

  1. 再往下我们对比demophone火焰图和M12的android::renderengine::skia::SkiaGLRenderEngine::drawLayersInternal 之后的流程是否存在差异, 仍然关注M12火焰图横轴占比最宽的函数, 一直到skgpu::v1::SurfaceDrawContext::addDrawOp 发现demophone火焰图和M12的存在差异, 如下图所示

  1. 可以看到在demophone的skgpu::v1::SurfaceDrawContext::addDrawOp 的函数下面并没有调用skgpu::v1::ClipStack::apply 方法, 同时我们看M12火焰图,这个apply方法也是非常耗时的.所以继续在代码中查找:发现在这里有个条件判断if(clip) 为true才会进入apply方法.

  1. 由此我们找到了代码流程中的主要差异, 我们进而继续添加trace, 发现耗时差异确实在此, M12的native包都没有打印apply trace,没有进入该方法

https://gerrit.pt.mioffice.cn/c/platform/external/skia/+/3050128

  1. 这时我们算是找到了root cause, 但后面需要怎么解决及优化就需要推动其它模块同事去处理, 可以看到clip是上层做的一些裁剪处理, 这是小米客制化的内容, 继续push解决中

我把clip这个false掉, 现在是一点不卡, 和原生&demophone没有差距了