Android Systrace 基礎知識(11) - Triple Buffer 解讀

本文是 Systrace 系列文章的第十一篇,主要是對 Systrace 中的 Triple Buffer 進行簡單介紹,簡單介紹瞭如何在 Systrace 中判斷卡頓情況的發生,進行初步的定位和分析,以及介紹 Triple Buffer 的引入對性能的影響

本系列的目的是通過 Systrace 這個工具,從另外一個角度來看待 Android 系統整體的運行,同時也從另外一個角度來對 Framework 進行學習。也許你看了很多講 Framework 的文章,但是總是記不住代碼,或者不清楚其運行的流程,也許從 Systrace 這個圖形化的角度,你可以理解的更深入一些。

系列文章目錄

  1. Systrace 簡介
  2. Systrace 基礎知識 - Systrace 預備知識
  3. Systrace 基礎知識 - Why 60 fps ?
  4. Systrace 基礎知識 - SystemServer 解讀
  5. Systrace 基礎知識 - SurfaceFlinger 解讀
  6. Systrace 基礎知識 - Input 解讀
  7. Systrace 基礎知識 - Vsync 解讀
  8. Systrace 基礎知識 - Vsync-App :基於 Choreographer 的渲染機制詳解
  9. Systrace 基礎知識 - MainThread 和 RenderThread 解讀
  10. Systrace 基礎知識 - Binder 和鎖競爭解讀
  11. Systrace 基礎知識 - Triple Buffer 解讀
  12. Systrace 基礎知識 - CPU Info 解讀

怎麼定義掉幀?

Systrace 中可以看到應用的掉幀情況,我們經常看到說主線程超過 16.6 ms 就會掉幀,其實不然,這和我們這一篇文章講到的 Triple Buffer 和一定的關係,一般來說,Systrace 中我們從 App 端和 SurfaceFlinger 端一起來判斷掉幀情況

App 端判斷掉幀

如果之前沒有看過 Systrace 的話,僅僅從理論上來說,下面這個 Trace 中的應用是掉幀了,其主線程的繪製時間超過了 16.6ms ,但其實不一定,因爲 BufferQueue 和 TripleBuffer 的存在,此時 BufferQueue 中可能還有上一幀或者上上一幀準備好的 Buffer,可以直接被 SurfaceFlinger 拿去做合成,當然也可能沒有

App 端掉幀
App 端掉幀

所以從 Systrace 的 App 端我們是無法直接判斷是否掉幀的,需要從 Systrace 裏面的 SurfaceFlinger 端去看

SurfaceFlinger 端判斷掉幀

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 出現掉幀情況的原因非常多,各位可以參考下面三篇文章食用:

  1. Android 中的卡頓丟幀原因概述 - 方法論
  2. Android 中的卡頓丟幀原因概述 - 系統篇
  3. Android 中的卡頓丟幀原因概述 - 應用篇

BufferQueue 和 Triple Buffer

BufferQueue

首先看一下 BufferQueue,BufferQueue 是一個生產者(Producer)-消費者(Consumer)模型中的數據結構,一般來說,消費者(Consumer) 創建 BufferQueue,而生產者(Producer) 一般不和 BufferQueue 在同一個進程裏面

BufferQueue
BufferQueue

其運行邏輯如下

  1. 當生產者(Producer) 需要 Buffer 時,它通過調用 dequeueBuffer()並指定 Buffer 的寬度,高度,像素格式和使用標誌,從 BufferQueue 請求釋放 Buffer
  2. 生產者(Producer) 將填充緩衝區,並通過調用 queueBuffer()將緩衝區返回到隊列。
  3. 消費者(Consumer) 使用 acquireBuffer()獲取 Buffer 並消費 Buffer 的內容
  4. 使用完成後,消費者(Consumer)將通過調用 releaseBuffer()將 Buffer 返回到隊列

Android 通過 Vsync 機制來控制 Buffer 在 BufferQueue 中的流動時機,如果對 Vsync 機制不瞭解,可以參考下面這兩篇文章,看完後你會有個大概的瞭解

  1. Systrace 基礎知識 - Vsync 解讀
  2. Android 基於 Choreographer 的渲染機制詳解

上面的流程比較抽象,這裏舉一個具體的例子,方便大家理解上面那張圖,對後續瞭解 Systrace 中的 BufferQueue 也會有幫助。

在 Android App 的渲染流程裏面,App 就是個生產者(Producer) ,而 SurfaceFlinger 是一個消費者(Consumer),所以上面的流程就可以翻譯爲

  1. App 需要 Buffer 時,它通過調用 dequeueBuffer()並指定 Buffer 的寬度,高度,像素格式和使用標誌,從 BufferQueue 請求釋放 Buffer
  2. App 可以用 cpu 進行渲染也可以調用用 gpu 來進行渲染,渲染完成後,通過調用 queueBuffer()將緩衝區返回到 App 對應的 BufferQueue(如果是 gpu 渲染的話,這裏還有個 gpu 處理的過程)
  3. SurfaceFlinger 在收到 Vsync 信號之後,開始準備合成,使用 acquireBuffer()獲取 App 對應的 BufferQueue 中的 Buffer 並進行合成操作
  4. 合成結束後,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 既要用來做合成顯示,又要被應用拿去做渲染

Single Buffer
Single Buffer

理想情況下,單 Buffer 是可以完成任務的(有 Vsync-Offset 存在的情況下)

  1. App 收到 Vsync 信號,獲取 Buffer 開始渲染
  2. 間隔 Vsync-Offset 時間後,SurfaceFlinger 收到 Vsync 信號,開始合成
  3. 屏幕刷新,我們看到合成後的畫面
Single Buffer 理想情況
Single Buffer 理想情況

但是很不幸,理想情況我們也就想一想,這期間如果 App 渲染或者 SurfaceFlinger 合成在屏幕顯示刷新之前還沒有完成,那麼屏幕刷新的時候,拿到的 Buffer 就是不完整的,在用戶看來,就有種撕裂的感覺

Single Buffer 非理想情況
Single Buffer 非理想情況

當然 Single Buffer 已經沒有在使用,上面只是一個例子

Double Buffer

Double Buffer 相當於 BufferQueue 中有兩個 Buffer 可供輪轉,消費者在消費 Buffer 的同時,生產者也可以拿到備用的 Buffer 進行生產操作

Double Buffer
Double Buffer

下面我們來看理想情況下,Double Buffer 的工作流程

DoubleBufferPipline_NoJank
DoubleBufferPipline_NoJank

但是 Double Buffer 也會存在性能上的問題,比如下面的情況,App 連續兩幀生產都超過 Vsync 週期(準確的說是錯過 SurfaceFlinger 的合成時機) ,就會出現掉幀情況

Double Buffer
Double Buffer

Triple Buffer

Triple Buffer 中,我們又加入了一個 BackBuffer ,這樣的話 BufferQueue 裏面就有三個 Buffer 可以輪轉了,當 FrontBuffer 在被使用的時候,App 有兩個空閒的 Buffer 可以拿去生產,就算生產過程中有 GPU 超時,CPU 任然可以拿到新的 Buffer 進行生產(即 SurfaceFling 消費 FrontBuffer,GPU 使用一個 BackBuffer,CPU 使用一個 BackBuffer)

Triple Buffer
Triple Buffer

下面就是引入 Triple Buffer 之後,解決了 Double Buffer 中遇到的由於 Buffer 不足引起的掉幀問題

解決第二個掉幀
解決第二個掉幀

這裏把兩個圖放到一起來看,方便大家做對比(一個是 Double Buffer 掉幀兩次,一個是使用 Triple Buffer 只掉了一幀)

TripleBuffer_VS_DoubleBuffer
TripleBuffer_VS_DoubleBuffer

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 之後,這種情況就基本上沒有了

降低 GPU 和 SurfaceFlinger 瓶頸
降低 GPU 和 SurfaceFlinger 瓶頸
降低 GPU 和 SurfaceFlinger 瓶頸
降低 GPU 和 SurfaceFlinger 瓶頸

Debug Triple Buffer

Dumpsys SurfaceFlinger

dumpsys SurfaceFlinger 可以查看 SurfaceFlinger 輸出的衆多當前的狀態,比如一些性能指標、Buffer 狀態、圖層信息等,後續有篇幅的話可以單獨拿出來講,下面是截取的 Double Buffer 情況下和 Triple Buffer 情況下的各個 App 的 Buffer 使用情況,可以看到不同的 App,在負載不一樣的情況下,對 Triple Buffer 的使用率是不一樣的;Double Buffer 則完全使用的是雙 Buffer

Dumpsys SurfaceFlinger
Dumpsys SurfaceFlinger

關閉 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

參考

  1. https://source.android.google.cn/devices/graphics

附件

本文涉及到的附件也上傳了,各位下載後解壓,使用 Chrome 瀏覽器打開即可 點此鏈接下載文章所涉及到的 Systrace 附件

關於我 && 博客

  1. 關於我 , 非常希望和大家一起交流 , 共同進步 .
  2. 博客內容導航
  3. 優秀博客文章記錄 - Android 性能優化必知必會
Android Performance
Android Performance

一個人可以走的更快 , 一羣人可以走的更遠

博客備案中,如果 com 無法訪問,可以訪問 cn AndroidPerformance

本文使用 mdnice 排版

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章