Systrace 流暢性實戰 2 :案例分析 - MIUI 桌面滑動卡頓分析

當咱們說 流暢度 的時候,咱們說的是什麼?不一樣的人對流暢性(卡頓掉幀)有不一樣的理解,對卡頓閾值也有不一樣的感知,因此有必要在開始這個系列文章以前,先把涉及到的內容說清楚,防止出現不一樣的理解,也方便你們帶着問題去看這幾篇問題,下面是一些基本的說明html

  1. 對手機用戶來講,卡頓包含了不少場景,好比在 滑動列表的時候掉幀應用啓動白屏過長點擊電源鍵亮屏慢界面操做沒有反應而後閃退點擊圖標沒有響應窗口動畫不連貫、滑動不跟手、重啓手機進入桌面卡頓 等場景,這些場景跟咱們開發人員所理解的卡頓還有點不同,開發人員會更加細分去分析這些問題,這是開發人員和用戶之間的一個認知差別,這一點在處理用戶(或者測試人員)的問題反饋的時候尤爲須要注意
  2. 對開發人員來講,上面的場景包括了 流暢度(滑動列表的時候掉幀、窗口動畫不連貫、重啓手機進入桌面卡頓)、響應速度(應用啓動白屏過長、點擊電源鍵亮屏慢、滑動不跟手)、穩定性(界面操做沒有反應而後閃退、點擊圖標沒有響應)這三個大的分類。之因此這麼分類,是由於每一種分類都有不太同樣的分析方法和步驟,快速分辨問題是屬於哪一類很重要
  3. 在技術上來講,流暢度、響應速度、穩定性(ANR)這三類之因此用戶感知都是卡頓,是由於這三類問題產生的原理是一致的,都是因爲主線程的 Message 在執行任務的時候超時,根據不一樣的超時閾值來進行劃分而已,因此要理解這些問題,須要對系統的一些基本的運行機制有必定的瞭解,本文會介紹一些基本的運行機制
  4. 流暢性這個系列主要是分析流暢度相關的問題,響應速度和穩定性會有專門的文章介紹,在理解了流暢性相關的內容以後,再去分析響應速度和穩定性問題會事半功倍
  5. 流暢性這個系列主要是講如何使用 Systrace (Perfetto) 工具去分析,之因此 Systrace 爲切入點,是由於影響流暢度的因素不少,有 App 自身的緣由、也有系統的緣由。而 (Perfetto) 工具能夠從一個整機運行的角度來展現問題發生的過程,方便咱們去初步定位問題

Systrace 流暢性實戰目前包括下面三篇android

  1. Systrace 流暢性實戰 1 :瞭解卡頓原理
  2. Systrace 流暢性實戰 2 :案例分析: MIUI 桌面滑動卡頓分析
  3. Systrace 流暢性實戰 3 :卡頓分析過程當中的一些疑問

Systrace (Perfetto) 工具的基本使用若是還不是很熟悉,那麼須要優先去補一下 Systrace 基礎知識系列git

Systrace 做爲分析卡頓問題的第一手工具,給開發者提供了一個從手機全局角度去看問題的方式,經過 Systrace 工具進行分析,咱們能夠大體肯定卡頓問題的緣由:是系統致使的仍是應用自身的問題github

固然 Systrace 做爲一個工具,再進行深刻的分析的時候就會有點力不從心,須要配合 TraceView + 源碼來進一步定位和解決問題,最後再使用 Systrace 進行驗證算法

因此本文更多地是講如何發現和分析卡頓問題,至於如何解決,就須要後續本身尋找合適的解決方案了,好比對比競品的 Systrace 表現、優化代碼邏輯、優化系統調度、優化佈局等chrome

案例說明

我的在使用小米 10 Pro 的時候,在桌面滑動這個最經常使用的場景裏面,總會有一種卡頓的感受,10 Pro 是 90Hz 的屏幕,FPS 也是 90,因此一旦出現卡頓,就會有很明顯的感受(我的對這個也比較敏感)。以前沒怎麼關注,在升級 12.5 以後,這個問題仍是沒有解決,因此我想看看究竟是怎麼回事shell

抓了 Systrace 以後分析發現,這個卡頓場景是一個很是好的案例,因此把這個例子拿出來做爲流暢度的一個實戰分享性能優化

建議你們下載附件中的 Systrace,對照文章食用最佳markdown

  1. 鑑於卡頓問題的影響因素比較多,因此在開始以前,我把本次分析所涉及的硬件、軟件版本溝通清楚,若是後續此場景有優化,此文章也不會進行修改,以文章附件中的 Systrace 爲準
  2. 硬件:小米 10 Pro
  3. 軟件:MIUI 12.5.3 穩定版
  4. 小米桌面版本:RELEASE-4.21.11.2922-03151646

Systrace 分析

分析卡頓問題,咱們通常的流程以下app

  1. 抓取 Systrace,能夠用 shell 或者手機自帶的工具來抓取
  2. 在 Chrome 中打開 Systrace 文件(html 結尾),若是不能直接打開,能夠在 Chrome 中輸入 chrome://tracing/,而後把 Systrace 文件拖到裏面就能夠打開
  3. 定位 App 進程在 Systrace 中的位置
  4. 定位問題發生點 - 通常以輸入事件,好比 input 事件
  5. 分析 App 進程的主線程和渲染線程
  6. 分析 SurfaceFlinger 進程的主線程和 Binder 線程
  7. 分析 SystemServer 進程的 Binder (不涉及能夠不用看)

按照這個流程分析以後,須要再反過來看各個進程,把各個線索聯繫起來,推斷最有可能的緣由

從 Input 事件開始

此次抓的 Systrace 我只滑動了一次,因此比較好定位,滑動的 input 事件由一個 Input Down 事件 + 若干個 Input Move 事件 + 一個 Input Up 事件組成

在 Systrace 中,SystemServer 中的 InputDispatcher 和 InputReader 線程都有體現,咱們這裏主要看在 App 主線程中的體現

如上圖,App 主線程上的 deliverInputEvent 標識了應用處理 input 事件的過程,input up 以後,就進入了 Fling 階段,這部分的基礎知識能夠查看下面這兩篇文章

  1. www.androidperformance.com/2019/11/04/…
  2. www.androidperformance.com/2020/08/20/…

分析主線程

因爲此次卡頓主要是鬆手以後纔出現的,因此咱們主要看 Input Up 以後的這段

主線程上面的 Frame 有顏色進行標註,通常有綠、黃、紅三種顏色,上面的 Systrace 裏面,沒有紅色的幀,只有綠色和黃色。那麼黃色就必定是卡頓麼?紅色就必定是卡頓麼?其實不必定,單單經過主線程,咱們並不能肯定是否卡頓,這個在下面會講

從主線程咱們無法肯定是否發生了卡頓,咱們找出了三個可疑的點,接下來咱們看一下 RenderThread

分析渲染線程

放大第一個可疑點,能夠看到,這一幀總耗時在 19ms, RenderThread 耗時 16ms,且 RenderThread 的 cpu 狀態都是 running(綠色),那麼這一幀這麼耗時的緣由大機率是下面兩個緣由致使的:

  1. RenderThread 自己耗時,任務比較繁忙
  2. RenderThread 的任務受 CPU 影響(多是頻率低了、或者是跑到小核了)

因爲只是可疑點,因此咱們先不去看 cpu 相關的,先查看 SurfaceFlinger 進程,肯定這裏有卡頓發生

分析 SurfaceFlinger

對於 Systrace 中 SurfaceFlinger 部分解讀不熟悉的能夠先預習一下這篇文章 www.androidperformance.com/2020/02/14/…

這裏咱們主要看兩個點

  1. App 對應的 BufferQueue 的 Buffer 狀況。經過這個咱們能夠知道在 SurfaceFlinger 端,App 是否有可用的 Buffer 提供給 SurfaceFlinger 進行合成
  2. SurfaceFlinger 主線程的合成狀況。經過查看 SurfaceFlinger 在 sf-vsync 到來的時候是否進行了合成工做,就能夠判斷這一幀是否出現了卡頓。

判斷是否卡頓的標準以下

  1. 若是 SurfaceFlinger 主線程沒有合成任務,並且 App 在這一個 Vsync 週期(vsync-app)進行了正常的工做,可是對應的 App 的 BufferQueue 裏面沒有可用的 Buffer,那麼說明這一幀卡了 — 卡頓出現 這種狀況以下圖所示(也是上圖中第一個疑點所在的位置)
  1. 若是 SurfaceFlinger 進行了合成,並且 App 在這一個 Vsync 週期(vsync-app)進行了正常的工做,可是對應的 App 的 BufferQueue 裏面沒有可用的 Buffer,那麼這一幀也是卡了,之因此 SurfaceFlinger 會正常合成,是由於有其餘的 App 提供了可用來合成的 Buffer — 卡頓出現 這種狀況以下圖所示(也在附件的 Systrace 裏面)

  2. 若是 SurfaceFlinger 進行了合成,並且 App 在這一個 Vsync 週期(vsync-app)進行了正常的工做,並且對應的 App 的 BufferQueue 裏面有可用的 Buffer,那麼這一幀就會正常合成,此時沒有卡頓出現 — 正常狀況 正常狀況以下,做爲對比仍是貼上來方便你們對比

回到本例的第一個疑點的地方,咱們經過 SurfaceFlinger 端的分析,發現這一幀確實是掉了,緣由是 App 沒有準備好可用的 Buffer 供 SurfaceFlinger 來合成,那麼接下來就須要看爲何這一幀 App 沒有可用的 Buffer 給到 SurfaceFlinger

回到渲染線程

上面咱們分析這一幀所對應的 MainThread + RenderThread 耗時在 19ms,且 RenderThread 耗時就在 16ms,那麼咱們來看 RenderThread 的狀況

出現這種狀況主要是有下面兩個緣由

  1. RenderThread 自己耗時,任務比較繁忙
  2. RenderThread 的任務受 CPU 影響(多是頻率低了、或者是跑到小核了)

可是桌面滑動這個場景,負載並不高,且鬆手以後並無多餘的操做,View 更新之類的,自己耗時比前一幀多了將近 3 倍,能夠推斷不是自身負載加劇致使的耗時

那麼就須要看此時的 RenderThread 的 cpu 狀況:

既然在 Running 狀況,咱們就去 CPU Info 區域查看這一段時間這個任務的調度狀況

分析 CPU 區域的信息

查看 CPU (Kernel 區域,這部分的基礎知識能夠查看 Android Systrace 基礎知識 - CPU Info 解讀Android Systrace 基礎知識 -- 分析 Systrace 預備知識)這兩篇文章

回到這個案例,咱們能夠看到 App 對應的 RenderThread 大部分跑在 cpu 2 和 cpu 0 上,也就是小核上(這個機型是高通驍龍 865,有四個小核+3 個大核+1 個超大核)

其此時對應的頻率也已經達到了小核的最高頻率(1.8Ghz)

且此時沒有 cpu boost 介入

那麼這裏咱們猜測,之因此這一幀 RenderThread 如此耗時,是由於小核就算跑滿了,也無法在這麼短的時間內完成任務

那麼接下來要驗證咱們的猜測,須要進行下面兩個步驟

  1. 對比其餘正常的幀,是否有跑在小核的。若是有且沒有出現掉幀,那麼說明咱們的猜測是錯誤的
  2. 對比其餘幾個異常的幀,看看掉幀的緣由是否也是由於 RenderThread 任務跑到了小核致使的。若是不是,那麼就須要作其餘的假設猜測

在用一樣的流程分析了後面幾個掉幀以後,咱們發現

  1. 對比其餘正常的幀,沒有在小核跑的,包括掉幀後的下一幀,調度器立刻把 RenderThread 擺到了大核,沒有出現連續掉幀的狀況
  2. 對比其餘幾個異常的幀,都是因爲 RenderThread 跑到了小核,可是小核的性能不足致使 RenderThread 執行耗時,最終引發卡頓

至此,這一次的卡頓分析咱們就找到了緣由:RenderThread 掉到了小核

至於 RenderThread 的任務爲啥跑着跑着就掉到了小核,這個跟調度器是有關係的,大小核直接的調度跟任務的負載有關係,任務從大核掉到小核、或者從小核遷移到大核,調度器這邊都是有參數和算法來控制的,因此後續的優化可能須要從這方面去入手

  1. 調整大小核遷移的閾值參數或者修改調度器算法
  2. 參考競品表現,看看競品在這個場景的性能指標,調度狀況等,分析競品可能使用的策略

Triple Buffer 在這個場景發揮了什麼做用?

Triple-Buffer-的做用 這篇文章,講到了 Triple Buffer 幾個做用

  1. 緩解掉幀
  2. 減小主線程和渲染線程等待時間
  3. 下降 GPU 和 SurfaceFlinger 瓶頸

那麼在桌面滑動卡頓這個案例裏面,Triple Buffer 發揮了什麼做用呢?結論是:有的場景沒有發揮做用,反而有反作用,致使卡頓現象更明顯,下面是分析流程

能夠看文章中 Triple Buffer 緩解掉幀 的原理:

在分析小米桌面滑動卡頓這個案例的時候,我發如今有一個問題,小米桌面對應的 App 的 BufferQueue,有時候會出現可用 Buffer 從 2 →0 ,這至關於直接把一個 Buffer 給拋棄掉了,以下圖所示

這樣的話,若是在後續的桌面 Fling 過程當中,又出現了一次 RenderThread 耗時,那麼就會以卡頓的形式直接體現出來,這樣也就失去了 Triple Buffer 的緩解掉幀的做用了

下圖能夠看到,因爲丟棄了一個 Buffer,致使再一次出現 RenderThread 耗時的時候,表現依然是無 Buffer 可用,出現掉幀

仔細看前面這段丟棄 Buffer 的邏輯,也很容易想到,這裏自己就已經丟了一幀了,還把這個耗時幀所對應的 Buffer 給丟棄了(也可能丟棄的是第二幀),不論是哪一種狀況,滑動時候的每一幀的內容都是計算好的(參考 List Fling 的計算過程),若是把其中一幀丟了,再加上自己 SurfaceFlinger 卡的那一下,卡頓感會很是明顯

舉個例子,以滑動爲例,offset 指的是離屏幕一個左邊的距離

  1. 正常狀況下,滑動的時候,offset 是:2→4→6→8→10→12
  2. 掉了一幀的狀況下,滑動的 Offset 是:2→4→6→6→8→10→12 (假設 計算 8 的這一幀超時了,就會看到兩個 6 ,這是掉了一幀的狀況)
  3. 像上圖裏面,若是直接扔掉了那個耗時的幀,就會出現下面這種 Offset:2→4→6→6→10→12 ,直接從 6 跳到了 10,至關於卡了 1 次,步子扯大了一次,感官上會以爲卡+跳躍

系列文章

  1. Systrace 流暢性實戰 1 :瞭解卡頓原理
  2. Systrace 流暢性實戰 2 :案例分析: MIUI 桌面滑動卡頓分析
  3. Systrace 流暢性實戰 3 :卡頓分析過程當中的一些疑問

附件

附件已經上傳到了 Github 上,能夠自行下載:github.com/Gracker/Sys…

  1. xiaomi_launcher.zip : 桌面滑動卡頓的 Systrace 文件,此次案例主要是分析這個 Systrace 文件
  2. xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑動的 Systrace 文件
  3. oppo_launcher_scroll.zip :對比文件

關於我 && 博客

  1. 關於我 , 很是但願和你們一塊兒交流 , 共同進步 .
  2. 博客內容導航
  3. 優秀博客文章記錄 - Android 性能優化必知必會

一我的能夠走的更快 , 一羣人能夠走的更遠