本文是 Systrace 系列文章的第十一篇,主要是對 Systrace 中的 Triple Buffer 進行簡單介紹,簡單介紹瞭如何在 Systrace 中判斷卡頓情況的發生,進行初步的定位和分析,以及介紹 Triple Buffer 的引入對性能的影響
本系列的目的是通過 Systrace 這個工具,從另外一個角度來看待 Android 系統整體的運行,同時也從另外一個角度來對 Framework 進行學習。也許你看了很多講 Framework 的文章,但是總是記不住代碼,或者不清楚其運行的流程,也許從 Systrace 這個圖形化的角度,你可以理解的更深入一些。
系列文章目錄
Systrace 簡介 Systrace 基礎知識 - Systrace 預備知識 Systrace 基礎知識 - Why 60 fps ? Systrace 基礎知識 - SystemServer 解讀 Systrace 基礎知識 - SurfaceFlinger 解讀 Systrace 基礎知識 - Input 解讀 Systrace 基礎知識 - Vsync 解讀 Systrace 基礎知識 - Vsync-App :基於 Choreographer 的渲染機制詳解 Systrace 基礎知識 - MainThread 和 RenderThread 解讀 Systrace 基礎知識 - Binder 和鎖競爭解讀 Systrace 基礎知識 - Triple Buffer 解讀 Systrace 基礎知識 - CPU Info 解讀
怎麼定義掉幀?
Systrace 中可以看到應用的掉幀情況,我們經常看到說主線程超過 16.6 ms 就會掉幀,其實不然,這和我們這一篇文章講到的 Triple Buffer 和一定的關係,一般來說,Systrace 中我們從 App 端和 SurfaceFlinger 端一起來判斷掉幀情況
App 端判斷掉幀
如果之前沒有看過 Systrace 的話,僅僅從理論上來說,下面這個 Trace 中的應用是掉幀了,其主線程的繪製時間超過了 16.6ms ,但其實不一定,因爲 BufferQueue 和 TripleBuffer 的存在,此時 BufferQueue 中可能還有上一幀或者上上一幀準備好的 Buffer,可以直接被 SurfaceFlinger 拿去做合成,當然也可能沒有
「所以從 Systrace 的 App 端我們是無法直接判斷是否掉幀的,需要從 Systrace 裏面的 SurfaceFlinger 端去看」
SurfaceFlinger 端判斷掉幀
SurfaceFlinger 端可以看到 SurfaceFlinger 主線程和合成情況和應用對應的 BufferQueue 中 Buffer 的情況。如上圖,就是一個掉幀的例子。App 沒有及時渲染完成,且此時 BufferQueue 中也沒有前幾幀的 Buffer,所以這一幀 SurfaceFlinger 沒有合成對應 App 的 Layer,在用戶看來這裏就掉了一幀
而在第一張圖中我們說從 App 端無法看出是否掉幀,那張圖對應的 SurfaceFlinger 的 Trace 如下, 可以看到由於有 Triple Buffer 的存在, SF 這裏有之前 App 的 Buffer,所以儘管 App 測一幀超過了 16.6 ms, 但是 SF 這裏依然有可用來合成的 Buffer, 所以沒有掉幀
邏輯掉幀
上面的掉幀我們是從渲染這邊來看的,這種掉幀在 Systrace 中可以很容易就發現;還存在一種掉幀情況叫「邏輯掉幀」
「邏輯掉幀」指的是由於應用自己的代碼邏輯問題,導致畫面更新的時候,不是以均勻或者物理曲線的方式,而是出現跳躍更新的情況,這種掉幀一般在 Systrace 上沒法看出來,但是用戶在使用的時候可以明顯感覺到
舉一個簡單的例子,比如說列表滑動的時候,如果我們滑動鬆手後列表的每一幀前進步長是一個均勻變化的曲線,最後趨近於 0,這樣就是完美的;但是如果出現這一幀相比上一幀走了 20,下一幀相比這一幀走了 10,下下一幀相比下一幀走了 30,這種就是跳躍更新,在 Systrace 上每一幀都是及時渲染且 SurfaceFlinger 都及時合成的,但是用戶用起來就是覺得會卡. 不過我列舉的這個例子中,Android 已經針對這種情況做了優化,感興趣的可以去看一下 android/view/animation/AnimationUtils.java 這個類,重點看下面三個方法的使用
public static void lockAnimationClock(long vsyncMillis)
public static void unlockAnimationClock()
public static long currentAnimationTimeMillis()
Android 系統的動畫一般不會有這個問題,但是應用開發者就保不齊會寫這種代碼,比如做動畫的時候根據**當前的時間(而不是 Vsync 到來的時間)**來計算動畫屬性變化的情況,這種情況下,一旦出現掉幀,動畫的變化就會變得不均勻,感興趣的可以自己思考一下這一塊
另外 Android 出現掉幀情況的原因非常多,各位可以參考下面三篇文章食用:
BufferQueue 和 Triple Buffer
BufferQueue
首先看一下 BufferQueue,BufferQueue 是一個生產者(Producer)-消費者(Consumer)模型中的數據結構,一般來說,消費者(Consumer) 創建 BufferQueue,而生產者(Producer) 一般不和 BufferQueue 在同一個進程裏面
其運行邏輯如下
當生產者(Producer) 需要 Buffer 時,它通過調用 dequeueBuffer()並指定 Buffer 的寬度,高度,像素格式和使用標誌,從 BufferQueue 請求釋放 Buffer 生產者(Producer) 將填充緩衝區,並通過調用 queueBuffer()將緩衝區返回到隊列。 消費者(Consumer) 使用 acquireBuffer()獲取 Buffer 並消費 Buffer 的內容 使用完成後,消費者(Consumer)將通過調用 releaseBuffer()將 Buffer 返回到隊列
Android 通過 Vsync 機制來控制 Buffer 在 BufferQueue 中的流動時機,如果對 Vsync 機制不瞭解,可以參考下面這兩篇文章,看完後你會有個大概的瞭解
上面的流程比較抽象,這裏舉一個具體的例子,方便大家理解上面那張圖,對後續瞭解 Systrace 中的 BufferQueue 也會有幫助。
在 Android App 的渲染流程裏面,App 就是個生產者(Producer) ,而 SurfaceFlinger 是一個消費者(Consumer),所以上面的流程就可以翻譯爲
當 「App」 需要 Buffer 時,它通過調用 dequeueBuffer()並指定 Buffer 的寬度,高度,像素格式和使用標誌,從 BufferQueue 請求釋放 Buffer 「App」 可以用 cpu 進行渲染也可以調用用 gpu 來進行渲染,渲染完成後,通過調用 queueBuffer()將緩衝區返回到 App 對應的 BufferQueue(如果是 gpu 渲染的話,這裏還有個 gpu 處理的過程) 「SurfaceFlinger」 在收到 Vsync 信號之後,開始準備合成,使用 acquireBuffer()獲取 App 對應的 BufferQueue 中的 Buffer 並進行合成操作 合成結束後,「SurfaceFlinger」 將通過調用 releaseBuffer()將 Buffer 返回到 App 對應的 BufferQueue
理解了 BufferQueue 的作用後,接下來來講解一下 BufferQueue 中的 Buffer
從上面的圖可以看到,BufferQueue 中的生產者和消費者通過 dequeueBuffer、queueBuffer、acquireBuffer、releaseBuffer 來申請或者釋放 Buffer,那麼 BufferQueue 中需要幾個 Buffer 來進行運轉呢?下面從單 Buffer,雙 Buffer 和 Triple Buffer 的角度分析(注意這裏只是從 Buffer 的角度來做分析的, 比如 App 測涉及到 Buffer 的是 RenderThread , 不過由於 RenderThread 與 MainThread 有一定的聯繫, 比如 unBlockUiThread 執行的時機, MainThread 也會因爲 RenderThread 執行慢而被 Block 住)
Single Buffer
單 Buffer 的情況下,因爲只有一個 Buffer 可用,那麼這個 Buffer 既要用來做合成顯示,又要被應用拿去做渲染
理想情況下,單 Buffer 是可以完成任務的(有 Vsync-Offset 存在的情況下)
App 收到 Vsync 信號,獲取 Buffer 開始渲染 間隔 Vsync-Offset 時間後,SurfaceFlinger 收到 Vsync 信號,開始合成 屏幕刷新,我們看到合成後的畫面
但是很不幸,理想情況我們也就想一想,這期間如果 App 渲染或者 SurfaceFlinger 合成在屏幕顯示刷新之前還沒有完成,那麼屏幕刷新的時候,拿到的 Buffer 就是不完整的,在用戶看來,就有種撕裂的感覺
當然 Single Buffer 已經沒有在使用,上面只是一個例子
Double Buffer
Double Buffer 相當於 BufferQueue 中有兩個 Buffer 可供輪轉,消費者在消費 Buffer 的同時,生產者也可以拿到備用的 Buffer 進行生產操作
下面我們來看理想情況下,Double Buffer 的工作流程
但是 Double Buffer 也會存在性能上的問題,比如下面的情況,App 連續兩幀生產都超過 Vsync 週期(準確的說是錯過 SurfaceFlinger 的合成時機) ,就會出現掉幀情況
Triple Buffer
Triple Buffer 中,我們又加入了一個 BackBuffer ,這樣的話 BufferQueue 裏面就有三個 Buffer 可以輪轉了,當 FrontBuffer 在被使用的時候,App 有兩個空閒的 Buffer 可以拿去生產,就算生產過程中有 GPU 超時,CPU 任然可以拿到新的 Buffer 進行生產(「即 SurfaceFling 消費 FrontBuffer,GPU 使用一個 BackBuffer,CPU 使用一個 BackBuffer」)
下面就是引入 Triple Buffer 之後,解決了 Double Buffer 中遇到的由於 Buffer 不足引起的掉幀問題
這裏把兩個圖放到一起來看,方便大家做對比(一個是 Double Buffer 掉幀兩次,一個是使用 Triple Buffer 只掉了一幀)
Triple Buffer 的作用
緩解掉幀
從上一節 Double Buffer 和 Triple Buffer 的對比圖可以看到,在這種情況下(出現連續主線程超時),三個 Buffer 的輪轉有助於緩解掉幀出現的次數(從掉幀兩次 -> 只掉幀一次)
所以從第一節如何定義掉幀這裏我們就知道,App 主線程超時不一定會導致掉幀,由於 Triple Buffer 的存在,部分 App 端的掉幀(主要是由於 GPU 導致),到 SurfaceFlinger 這裏未必是掉幀,這是看 Systrace 的時候需要注意的一個點
減少主線程和渲染線程等待時間
「雙 Buffer 的輪轉」, App 主線程有時候必須要等待 SurfaceFlinger(消費者)釋放 Buffer 後,才能獲取 Buffer 進行生產,這時候就有個問題,現在大部分手機 SurfaceFlinger 和 App 同時收到 Vsync 信號,如果出現 App 主線程等待 SurfaceFlinger(消費者)釋放 Buffer ,那麼勢必會讓 App 主線程的執行時間延後,比如下面這張圖,可以明顯看到:「Buffer B 並不是在 Vsync 信號來的時候開始被消費(因爲還在使用),而是等 Buffer A 被消費後,Buffer B 被釋放,App 才能拿到 Buffer B 進行生產,這期間就有一定的延遲,會讓主線程可用的時間變短」
我們來看一下在 Systrace 中的上面這種情況發生的時候的表現
而 三個 Buffer 輪轉的情況下,則基本不會有這種情況的發生,渲染線程一般在 dequeueBuffer 的時候,都可以順利拿到可用的 Buffer (當然如果 dequeueBuffer 本身耗時那就不是這裏的討論範圍了)
降低 GPU 和 SurfaceFlinger 瓶頸
這個比較好理解,雙 Buffer 的時候,App 生產的 Buffer 必須要及時拿去讓 GPU 進行渲染,然後 SurfaceFlinger 才能進行合成,一旦 GPU 超時,就很容易出現 SurfaceFlinger 無法及時合成而導致掉幀
在三個 Buffer 輪轉的時候,App 生產的 Buffer 可以及早進入 BufferQueue,讓 GPU 去進行渲染(因爲不需要等待,就算這裏積累了 2 個 Buffer,下下一幀纔去合成,這裏也會提早進行,而不是在真正使用之前去匆忙讓 GPU 去渲染),另外 SurfaceFlinger 本身的負載如果比較大,三個 Buffer 輪轉也會有效降低 dequeueBuffer 的等待時間
比如下面兩張圖,就是對應的 SurfaceFlinger 和 App 的「雙 Buffer 掉幀」情況,由於 SurfaceFlinger 本身就比較耗時(特定場景),而 App 的 dequeueBuffer 得不到及時的響應,導致發生了比較嚴重的掉幀情況。在換成 Triple Buffer 之後,這種情況就基本上沒有了
Debug Triple Buffer
Dumpsys SurfaceFlinger
dumpsys SurfaceFlinger 可以查看 SurfaceFlinger 輸出的衆多當前的狀態,比如一些性能指標、Buffer 狀態、圖層信息等,後續有篇幅的話可以單獨拿出來講,下面是截取的 Double Buffer 情況下和 Triple Buffer 情況下的各個 App 的 Buffer 使用情況,可以看到不同的 App,在負載不一樣的情況下,對 Triple Buffer 的使用率是不一樣的;Double Buffer 則完全使用的是雙 Buffer
關閉 Triple Buffer
不同 Android 版本屬性設置不一樣(這是 Google 的一個邏輯 Bug,Android 10 上面已經修復了)
Android 版本 <= Android P
//控制代碼
property_get("ro.sf.disable_triple_buffer", value, "1");
mLayerTripleBufferingDisabled = atoi(value);
ALOGI_IF(mLayerTripleBufferingDisabled, "Disabling Triple Buffering");
「修改對應的屬性值,然後重啓 Framework」
//按順序執行下面的語句(需要 Root 權限)
adb root
adb shell setprop ro.sf.disable_triple_buffer 0
adb shell stop && adb shell start
Android 版本 > Android P
//控制代碼
property_get("ro.sf.disable_triple_buffer", value, "0");
mLayerTripleBufferingDisabled = atoi(value);
ALOGI_IF(mLayerTripleBufferingDisabled, "Disabling Triple Buffering");
「修改對應的屬性值,然後重啓 Framework」
//按順序執行下面的語句(需要 Root 權限)
adb root
adb shell setprop ro.sf.disable_triple_buffer 1
adb shell stop && adb shell start
參考
附件
本文涉及到的附件也上傳了,各位下載後解壓,使用 「Chrome」 瀏覽器打開即可 點此鏈接下載文章所涉及到的 Systrace 附件
關於我 && 博客
關於我 , 非常希望和大家一起交流 , 共同進步 . 博客內容導航 優秀博客文章記錄 - Android 性能優化必知必會
「一個人可以走的更快 , 一羣人可以走的更遠」
博客備案中,如果 com 無法訪問,可以訪問 cn AndroidPerformance
本文使用 mdnice 排版