问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Android性能分析:卡顿丢帧基础CPU/GPU原理

创作时间:
作者:
@小白创作中心

Android性能分析:卡顿丢帧基础CPU/GPU原理

引用
CSDN
1.
https://blog.csdn.net/zhangphil/article/details/138804486

Android显示系统中,包括 CPU、GPU、display 三部分。CPU 计算数据然后交给 GPU 渲染,渲染好后放到 buffer 中存起来,然后 display 将 buffer 数据显示到屏幕上。

渲染过程中的CPU和GPU分工

渲染的2个核心:CPU 和 GPU。

  • CPU:负责Measure、Layout等操作。
  • GPU:负责Rasterization(栅格化)。

在GPU中有一块缓冲区叫做 Frame Buffer,这个帧缓冲区可以认为是存储像素值的二维数组。GPU 除了帧缓冲区用以交给手机屏幕进行绘制外,还有一个缓冲区 Back Buffer ,这个用以交给应用的,让CPU往里面填充数据。

GPU会定期交换 Back Buffer 和 Frame Buffer ,也就是对Back Buffer中的数据进行栅格化后将其转到 Frame Buffer,然后交给屏幕进行显示,同时让原先的Frame Buffer 变成 Back Buffer 让程序处理。

渲染过程中可能出现的问题

tearing(撕裂)

当 CPU/GPU 将数据准备好存入 buffer 中,但 display 还没来得及显示,这时 CPU/GPU 把下一帧的数据往 buffer 中写,还没写完的时候,display 开始读取 buffer 来显示(也就是绘图速度大于显示速度)。这时就会出现显示的上半部分是下一帧的数据,下半部分为上一帧的数据,就是所说的撕裂。

jank(丢帧)

绘图速度过慢的时候,同一帧在屏幕上至少出现2次。

解决方案:双缓冲与VSYNC

撕裂的原因是 display 还没来得及读, buffer 就被重写了,那么就可以准备2个 buffer 即双缓冲。back buffer 用于 CPU/GPU 后台绘制,front buffer 用于显示。back buffer 准备好后才可以交换,这样就可以避免撕裂问题。但是此时屏幕还没有完整显示上一帧的内容时是不能交换的。那么只有等屏幕处理完成当前帧才能进行交换操作。当扫描完一屏后,会回到第一行进入下一次的循环,中间会有一段空隙(VBI),这个空隙为缓冲区交换的最佳时间。VSYNC 就是利用这个空隙出现的垂直刷新脉冲来保证双缓冲的最佳时间点。

双缓冲下,没有 VSYNC 的丢帧情况:

Display 为显示屏, VSYNC 仅指双缓冲的交换:

  • Step1:Display 显示第0帧,此时 CPU/GPU 渲染第1帧画面,并且在 Display 显示下一帧前完成。
  • Step2:Display 正常渲染第一帧。
  • Step3:出于某种原因,如 CPU 资源被占用,系统没有及时处理第2帧数据,当 Display 显示下一帧时,由于数据没处理完,所以依然显示第1帧,即发生Jank。

是因为第2帧没有在显示前及时处理,导致屏幕多显示第1帧一次,导致后面的帧都延时了。

引入VSYNC后的理想绘制情况

当且仅当 VSYNC 出现时,CPU 就会立即处理下一帧数据,大大降低 Jank 概率。而且杜绝了 CPU/GPU 不停的绘制,导致帧生成速度高于屏幕刷新速度,生成的帧不能显示而被丢弃,这样导致的丢帧情况。

引入 VSYNC 后,绘制速度和屏幕刷新速度保持一致。早期经典的Android 设备屏幕刷新频率为 60HZ,那么 CPU/GPU 渲染的时间需要在 16ms 内完成。当 CPU/GPU 的 FPS 高于 60 HZ 显示效果会很完美。

当VSync 信号产生时,先完成Back Buffer 到 Frame Buffer的复制操作(交换内存地址),然后通知 CPU/GPU 绘制下一帧图像。也只有VSync 信号发生时,才绘制下一帧。将绘制工作都统一到VSYNC时间点上,是Choreographer的作用,在Choreographer指挥下,CPU/GPU绘制工作井井有条。但如果设备硬件性能较差,无法达到也会丢帧:

超时了,刷新频率>帧率,此时刷新屏幕,发出VSYNC 信号,由于CPU/GPU的渲染操作还没有完成,就不把Back Buffer的数据复制到 Frame Buffer,此时就从Frame Buffer去取旧数据,这样在两个刷新周期里,显示的是同一帧数据。

双缓冲区的丢帧

当第1个 VSYNC 到来时 GPU 还在处理数据,这时 B 缓冲区被占用了,那么就无法进行交换,屏幕依然显示 A 缓冲区数据。下一个信号到来时,此时 GPU 已经处理完了,那么就可以交换缓冲区,此时屏幕显示 B 缓冲区,CPU/GPU 开始操作 A。下一个信号到来时,A 被占用,那么屏幕依然显示 B 的数据。这种情况就是因为 GPU/CPU 无法在 16ms 内处理完数据而导致缓冲区交换延迟。

第一次的Jank看起来是没有办法的,除非升级硬件配置来加快FPS。设备不能升级硬件,无法改变 CPU/GPU 渲染时间,那么第一次 Jank 是无法避免的。重点关注 CPU 第一次和第二次执行中间浪费的时间。当第1次信号到来时,由于 GPU 占用了 B,导致屏幕会一直占用 A。两个缓冲区都被占用了,即使此时 CPU 是空闲的,它也没有办法处理下一帧的数据。当 CPU/GPU 绘制一帧的时间超过 16 ms 时,产生 Jank。产生 Jank 的那一帧的显示期间,GPU/CPU 在闲置的。造成CPU/GPU无事可做的假象是因为当前已经没有可用的buffer了。如果有第三个 Buffer 能让 CPU/GPU 在这个时候继续工作,那就完全可以避免第二个 Jank 的发生。

增加一个 buffer,三缓冲

当第一个信号到来时,A、B 都被占用,此时 CPU 开始使用 C 缓冲区来处理下一帧数据。之前第二次发生的 Jank 就避免了。双缓冲和三重缓冲都会有 lag(延时)问题,C 缓冲区延时了16ms才显示。

获取丢帧率的adb shell命令

adb shell dumpsys SurfaceFlinger packagename

卡顿丢帧除了CPU/GPU层面,另外,也需要特别注意整机低内存情况。kswapd0 是一个内核工作线程,内存不足时会被唤醒,做内存回收工作。 当内存频繁在低水位的时候,kswapd0 会被频繁唤醒,占用 cpu ,造成卡顿和耗电。通常kswapd0占用大核,而且是满频在跑,耗电、温升,如果此时有前台应用主线程跑到与kswapd0相同的核上,很大可能会出现 cpu 竞争,导致调度不到而丢帧卡顿。HeapTaskDaemon 通常也会在低内存时候跑的很高。

adb shell dumpsys meminfo
adb shell free
adb shell cat /proc/meminfo

当系统处于低内存的情况时候 , MemFree 和 MemAvailable 的值都很小。

  • MemTotal:内存总数
  • MemFree:空闲内存数
  • Cached:缓存区内存数
  • MemAvailable:可用内存数
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号