iOS 底層原理39:Instruments系列(三)Animation Hitches

iOS 底層原理 文章匯總

引言

從Xcode12開始沐扳,Instrument更新了UI,新增了一個模板 Animation Hitches 用來分析用戶的 app 中的卡頓俯树,并去除了 Core Animation 檢測方式。在 iPhone13Pro 之前 iPhone 屏幕最高刷新頻率仍為 60 HZ才避,而在支持 PromotionDisplay 的設(shè)備上幀率可調(diào)整至 120 幀,并且會根據(jù)當(dāng)前用戶手勢和設(shè)備狀態(tài)進行動態(tài)調(diào)整。此時再繼續(xù)使用幀率來判斷性能的好壞及流暢度將會是一個錯誤的選擇。所以 Animation Hitches 主要用于代替幀率檢測烘嘱,并且提出 卡頓時間比(Hitch Time Ratio) 的概念用于替代 FPS。

在 Hitch 提出之前蝗蛙,都是借助FPS (Frames Per Second 幀率)蝇庭,即每秒繪制幀的數(shù)量來衡量頁面是否卡頓

  • 滑動屏幕時,幀率理想值為60FPS
  • 滑動屏幕時捡硅,幀率越高表示性能越好哮内;幀率過低意味著屏幕可能出現(xiàn)卡頓,存在隨機丟幀的可能壮韭。
  • 其中幀率>=57為優(yōu)秀; >=55為良好; >=50為可接受;

蘋果于 20 年的 Session 中提出了 Hitch 的概念北发,用以衡量滑動時的卡頓情況。Hitch 指的是 卡頓時間(一幀延后出現(xiàn)的時間喷屋,ms)/ 總時間(一般是 1 秒)琳拨,簡單來說 卡頓時間比就是一個區(qū)間內(nèi)的總卡頓時間除以它的持續(xù)時間。

  • 低于 5 ms/s 說明比較優(yōu)秀屯曹,是最不易被用戶察覺到的
  • 介于 5ms/s 和 10ms/s 之間狱庇,說明發(fā)生了中等卡頓,用戶會察覺到一些中斷恶耽,但并不嚴重
  • 高于 10 ms/s 說明發(fā)生了較嚴重的卡頓密任,已經(jīng)影響了用戶體驗。
    Hitch

卡頓

  • 概念:任何時候屏幕上出現(xiàn)晚于預(yù)計的幀都屬于卡頓
  • 簡單來說就是掉幀了偷俭,即沒有在規(guī)定時間內(nèi)渲染好一幀畫面浪讳,這就是卡頓一次


    卡頓

如上圖所示,當(dāng)手指在屏幕上滑動時涌萤,滾動視圖會隨著手勢做出響應(yīng)淹遵,如果一幀一幀來看,就是每一幀都對應(yīng)手指位置的變化负溪。當(dāng)卡頓發(fā)生時合呐,某一幀沒有跟隨手指變化,導(dǎo)致到下一幀時笙以,產(chǎn)生跳躍淌实,打破了用戶和屏幕內(nèi)容的視覺連接感。圖中卡頓產(chǎn)生的原因就是第三幀重復(fù)了猖腕,主要是因為第四幀的延遲導(dǎo)致了第三幀占用了兩幀的時間拆祈,給用戶看到的就是卡頓掉幀的現(xiàn)象。

RenderLoop

  • 概念:是一個連續(xù)的過程倘感,通過用戶手勢等將事件傳給 App放坏,接著 App 向操作系統(tǒng)傳遞事件并最終響應(yīng)事件,再將響應(yīng)傳遞給用戶的過程


    RenderLoop

    RenderLoop 的時間隨著設(shè)備刷新頻率老玛,在 iPhone13 Pro(Max) 以下的 iPhone 設(shè)備最大均為 60 幀淤年,而 iPhone13 Pro(Max) 及 iPadPro 則最高支持 120 幀钧敞,也就是最短僅需每 8.33 毫秒就可以顯示一個新幀。


    RenderLoop刷新頻率

視圖渲染流程

在每一幀顯示的過程中麸粮,大概可以分為3個階段溉苛,如下所示


渲染流程
  • App:進行用戶事件處理
  • Render server:負責(zé)將圖層樹轉(zhuǎn)換為可顯示的圖像(即用戶界面繪制)
  • On the display:顯示緩存的幀


1、AppRender server 階段需要在下一個 VSYNC 到來之前完成
2弄诲、這里運用了 雙緩存區(qū) + 垂直同步機制愚战,主要是用于解決 屏幕撕裂現(xiàn)象
3、整個渲染階段可以分為5個階段:

渲染階段細分

  • 階段 1 + 2:App(Event + Commit)
  • 階段 3 + 4:Render server(Prepare + Excute)
  • 階段 5:Display

4齐遵、整體渲染流程如下

  • Event(事件階段)通過 touch寂玲、timer 等事件決定用戶界面是否需要改變
  • Commit(提交階段),App會向 Render server(渲染服務(wù)器)提交渲染命令
  • Prepare (準備階段)會為 GPU 的繪制組好準備
  • Excute(執(zhí)行階段)會由 GPU 將用戶界面的圖像繪制出來
  • Display(顯示階段)會將緩沖區(qū)的幀交換到屏幕上顯示

下面以一個帶有陰影的渲染圖形為例梗摇,通過觀察 RenderLoop 中每一幀所做的工作拓哟,來分別介紹不同階段

App 階段

App 階段包含 2 個階段,分別是 Event 伶授、Commit彰檬。其中Commit 又分為 4 個子階段,分別是

  • Layout
  • Display
  • Prepare
  • Commit

階段 1:Event 事件階段

  • 事件階段 表示 App 接收到了事件(例如:touch谎砾、網(wǎng)絡(luò)請求回調(diào)逢倍、鍵盤、timer等)景图。


    事件階段-Event
  • 在 App 中可以通過改變其層級結(jié)構(gòu)较雕,或者使用其他方式響應(yīng)事件。例如圖層顏色/大小/位置變化挚币。當(dāng) App 更新了圖層時亮蒋, CoreAnimation 會同時調(diào)用 setNeedsLayout 方法,該方法能夠找出哪些圖層需要重新計算布局妆毕,且系統(tǒng)會合并這些重新計算的請求慎玖,并在 Commit 階段按需執(zhí)行,以此來減少重復(fù)工作

    層級變化

階段 2: Commit 提交階段

提交階段還可以細分笛粘,主要分為4個子階段


提交階段-Commit
  • layout (布局階段):layoutSubviews 會被所有需要布局的 View 調(diào)用
  • display(顯示階段):drawRect 會被每個需要被更新的 View 調(diào)用
  • prpare(準備階段):未解碼圖像進一步解碼(即需要優(yōu)化的常見的圖片主線程解碼操作)
  • commit(提交階段):視圖樹將會被遞歸打包并發(fā)送到 RenderServer 中
Layout - 布局階段

在布局階段趁怔,layoutSubviews 會被所有需要布局的 View 調(diào)用。例如視圖布局(frame薪前、bounds润努、tranform等)、增加/移除視圖示括、直接調(diào)用 setNeedsLayout/layoutIfNeesed 等

注:這些操作并不是立即執(zhí)行铺浇,系統(tǒng)會合并布局請求,在 Runloop 休眠前進行統(tǒng)一處理

Display - 顯示階段
  • 在顯示階段垛膝,drawRect 會被每個需要被更新的 View 調(diào)用鳍侣,例如 UILabel 等空間類或者 任何重寫 drawRect 方法 的類丁稀,必須調(diào)用 調(diào)用 setNeedsDisplay 用以支持 View 的更新。
  • 非必須不要重寫 drawRect 方法倚聚,因為在繪制時线衫,每個自定義圖層都會接收到帶紋理的 CoreGraphics 的背景,會利用 CoreAnimation 進行繪制秉沼,這些圖層就變成了圖片
    • 導(dǎo)致內(nèi)存額外的開銷以及bitmap的存儲桶雀,對整體內(nèi)存壓力較大
    • 由于是在 CPU上進行繪制矿酵,還增加了整體主線程的占用
drawRect
Prepare - 準備階段

在準備階段唬复,主要是將還未解碼的圖像進行進一步解碼,這也是我們需要優(yōu)化的點(即優(yōu)化圖片主線程解碼操作)全肮。

因為對于每個解碼的圖像敞咧,App可能會持續(xù)存在大量的內(nèi)存分配(與圖像大小成正比),當(dāng)App占用內(nèi)存越來越多時辜腺,操作系統(tǒng)就會開始壓縮物理內(nèi)存(physical memory)休建,這整個過程都需要CPU參與,所以除了App會使用CPU评疗,還增加了無法控制的全局 CPU 使用率测砂,導(dǎo)致App消耗更多的物理內(nèi)存,此時操作系統(tǒng)會終止低優(yōu)先級的后臺進程百匆,從而釋放更多的物理內(nèi)存砌些。但設(shè)備的物理內(nèi)存始終是有限的,當(dāng)App對內(nèi)存的消耗達到了臨界值時加匈,該App進程就會被操作系統(tǒng)終止存璃,這就是常說的大圖導(dǎo)致的OOM

若某個圖像的顏色格式 GPU 無法直接使用雕拼,也會在這一步進行格式轉(zhuǎn)換纵东。這就要求對該圖像進行 copy 操作,而不是直接使用指針啥寇,這樣會耗時更長及占用更多的內(nèi)存偎球。

Commit - 提交階段

在提交階段,視圖樹會被遞歸打包辑甜,并發(fā)送到 Render Server中甜橱,所以當(dāng)視圖圖層較復(fù)雜時,這個過程的耗時也會相對較長栈戳,這也是我們經(jīng)常提及的優(yōu)化點(即盡量減輕視圖層級結(jié)構(gòu)岂傲,不要跟套娃似的,無窮無盡)子檀。

Render Server 階段

Render Server(渲染服務(wù)器)主要負責(zé)將圖層樹轉(zhuǎn)換為真正顯示的圖像镊掖,分為兩個子階段


Render Server 階段
  • prepare:圖層樹被編譯成一系列簡單的指令乃戈,供 GPU 執(zhí)行,幀動畫也在此處進行處理
  • excute:GPU 將 App 的圖層繪制成最終圖像

階段 3:Prepare 準備階段

  • 在 準備階段亩进,RenderServer 會廣度優(yōu)先遍歷 App 的圖層樹症虑,準備一個線性管線,這樣 GPU 就能按照順序執(zhí)行命令進行繪制归薛。


    遍歷圖層樹
  • 從根圖層開始逐層遍歷谍憔,最終才有了 GPU 可以在下一個執(zhí)行階段執(zhí)行的整個管線。


    逐層遍歷

階段 4:Excute 執(zhí)行階段

在執(zhí)行階段主籍,主要是由 GPU 根據(jù)前面 prepare 階段準備好的圖層樹進行頂點著色习贫、形狀裝配、幾何著色千元、光柵化苫昌、片段著色與圖層混合。一旦 GPU 執(zhí)行完會將渲染好的圖像放入幀緩存區(qū)中等待下一個 VSYNC 的到來并交換到屏幕上進行顯示幸海。

執(zhí)行階段-Excute

Display 階段

階段 5:Display 顯示階段

在顯示階段祟身,主要是將幀緩存區(qū)中的內(nèi)容交換到顯示器上進行最終顯示

視圖渲染流程總結(jié)

  • App:進行用戶事件的處理
    • Event:App接收到事件(touch、網(wǎng)絡(luò)請求物独、鍵盤袜硫、timer等)
    • Commit
      • layout (布局階段):layoutSubviews 會被所有需要布局的 View 調(diào)用
      • display(顯示階段):drawRect 會被每個需要被更新的 View 調(diào)用
      • prpare(準備階段):未解碼圖像進一步解碼(即需要優(yōu)化的常見的圖片主線程解碼操作)
      • commit(提交階段):視圖樹將會被遞歸打包并發(fā)送到 RenderServer 中
  • RenderServer:負責(zé)將圖層樹轉(zhuǎn)換為可顯示的圖像(即用戶界面繪制)
    • prepare:圖層樹被編譯成一系列簡單的指令,供 GPU 執(zhí)行挡篓,幀動畫也在此處進行處理
    • excute:GPU 將 App 的圖層繪制成最終圖像
  • Display:將緩沖的幀顯示出來

想了解離屏渲染的同學(xué)請閱讀# 屏幕卡頓 及 iOS中的渲染流程解析

卡頓類型

通過了解了視圖渲染的工作流程婉陷,其主要工作是在App 和 Render Server 中進行的,所以總共涉及兩種卡頓類型

  • 提交卡頓(App 階段)
  • 渲染卡頓(Render Server 階段)
    卡頓類型

提交卡頓

  • 提交卡頓:是指 App 話費過長的時間來處理/提交事件
    提交卡頓

    如上圖所示瞻凤,在提交階段耗時過長憨攒,從而導(dǎo)致錯過了截止時間,所以在下一個 VSYNC 中 Render Server 沒有需要處理的事情阀参,必須要等待下一個 VSYNC 到了后才開始渲染肝集。簡單來說就是把幀傳送的時間延遲了一幀(即 16.67ms),這個延遲時間 即為 卡頓時間(Hitch time)蛛壳。

如何避免提交卡頓杏瞻?

主要有以下幾種方式

  • 保持視圖輕量
  • 避免復(fù)雜布局
  • 合理運用多線程能力

下面進行詳細說明

保持視圖的輕量
  • 盡可能利用 CALayer 上GPU 加速的可用屬性,如非必要避免使用CPU進行自定義繪制
  • 非必要情況下衙荐,避免重寫 drawRect 方法捞挥,因為會導(dǎo)致額外的內(nèi)存開銷。

針對于文本忧吟、圖片等原本就在 CPU 上進行繪制的系統(tǒng)控件砌函,我們可以嘗試使用其更底層線程安全的 CoreGraphics 能力,比如 TextKit、CoreText 等搭配多線程異步繪制減輕主線程壓力讹俊。

  • 盡量復(fù)用視圖垦沉,避免重復(fù)的添加/移除視圖
  • 如果需要將某一個視圖從某一個動畫中移除,盡量使用 hidden 屬性
  • 對于 Prepare 階段仍劈,當(dāng)我們的 UIImage 容器視圖的大小小于圖片本身時厕倍,我們通常可以使用 下采樣技術(shù)(downsampling) 來進行縮略圖的創(chuàng)建以節(jié)省部分內(nèi)存空間贩疙。
避免復(fù)雜布局
  • 減少代價過高且重復(fù)的布局讹弯,在需要更新布局時盡量只使用 setNeedsLayout

layoutIfNeeded 會消耗當(dāng)前事務(wù)的生命周期也會造成卡頓这溅,大多數(shù)時候你可以等到下一次 Runloop 執(zhí)行時再更新你的布局组民。

  • 避免復(fù)雜布局約束,嘗試使用最少的約束來完成布局
  • 避免遞歸布局芍躏,即視圖應(yīng)該只能使自己或自己的子視圖無效邪乍,而不能使其同級視圖或父視圖無效
  • 避免非必要的視圖層級降狠,復(fù)雜的視圖層級會增加提交階段的整體耗時
合理運用多線程能力
  • 利用GCD的多線程能力对竣,充分利用 CPU 多核優(yōu)勢,提前在子線程進行布局等 UI 無關(guān)操作榜配,避免主線程掛起(hang)否纬。
  • 避免主線程 IO 等磁盤相關(guān)操作
  • 針對于常見的主線程解碼操作,
    • 在 iOS15 之前蛋褥,我們通常都是自己封裝或是利用最常見的第三方庫 SDWebImage 替我們在子線程進行解碼操作临燃。
    • 在 iOS15 中,Apple 終于提供了官方的解決方案以解決該問題:UIImage 的 prepareThumbnailOfSize:completionHandler: 等新接口烙心。
  • 針對于必須在 CPU 上進行繪制的組件膜廊,嘗試結(jié)合多線程使用異步繪制能力減輕主線程壓力。

渲染卡頓

  • 渲染卡頓:是指 Render Server 無法按時準備/執(zhí)行圖層樹的出現(xiàn)淫茵,即 Excute 階段耗時超過了 VSYNC 的界限爪瓜,導(dǎo)致本來應(yīng)該渲染的幀為準備好。


    渲染卡頓

    如上圖所示匙瘪,綠色的畫面比預(yù)期的晚了一幀于是有了 16 毫秒的卡頓铆铆。

如何避免渲染卡頓?

Prepare 階段對卡頓的影響較少丹喻,主要還是在 Excute 階段的離屏渲染薄货。針對離屏渲染的優(yōu)化,請閱讀
# iOS 常見觸發(fā)離屏渲染場景及優(yōu)化方案總結(jié)

  • 對于陰影來說碍论,在設(shè)置陰影時確保設(shè)置 shadowPath 以減少大量離屏通道
  • 在圓化矩形時谅猾,使用 cornerRadiuscornerCurve 屬性避免用蒙版或角內(nèi)容來構(gòu)成圓角矩形。
  • 優(yōu)化整個 App 的 Mask。

使用 masksToBounds 遮蔽為矩形圓角矩形或橢圓形的性能比自定義蒙版圖層好得多

  • 合理并謹慎的使用 shouldRasterize 屬性税娜,

它會對一塊圖層進行光柵化操作并進行緩存先煎。若針對于需要頻繁刷新的圖層使用該屬性反而對性能有著負面影響。

  • 盡量使用非透明的圖層
  • 盡量減少圖層混合
  • 重要的是用 Instruments 來對 App 進行分析并檢查圖層樹以獲得重要的技巧從而降低整體離屏計數(shù)巧涧。

下面就主要介紹 Instrument 中 Animation Hitches 的使用

使用

  • 選中 Instrument 中的 Animation Hitches

    Animation Hitches

  • 啟動程序薯蝎,會顯示recording 此時操作界面卡頓的位置工具會記錄


    記錄
  • 然后再次點擊關(guān)閉等待Analyze分析完成后顯示如下界面,找出耗時的函數(shù)


    耗時函數(shù)
  • 最后分析谤绳,并根據(jù)實際情況解決問題

參考文章

Tech Talk - Hitches 與 渲染循環(huán)
iOS 性能檢測新方式——AnimationHitches
Animation Hitches in iOS Development
Explore UI animation hitches and the render loop - Tech Talks - Videos - Apple Developer
Find and fix hitches in the commit phase - Tech Talks - Videos
iOS 性能分析-阿里
iOS 高刷屏監(jiān)控 + 優(yōu)化:從理論到實踐全面解析 -字節(jié)
WWDC20 10077 - 使用 XCTest 消除動畫卡頓
# APP 性能優(yōu)化終極求生指南
# iOS性能優(yōu)化之界面卡頓監(jiān)測
# iOS 高刷屏監(jiān)控 + 優(yōu)化:從理論到實踐全面解析
## 精確定位頁面滑動幀率瓶頸及優(yōu)化參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末占锯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缩筛,更是在濱河造成了極大的恐慌消略,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞎抛,死亡現(xiàn)場離奇詭異艺演,居然都是意外死亡,警方通過查閱死者的電腦和手機桐臊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門胎撤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人断凶,你說我怎么就攤上這事伤提。” “怎么了认烁?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵肿男,是天一觀的道長。 經(jīng)常有香客問我却嗡,道長舶沛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任窗价,我火速辦了婚禮如庭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舌镶。我一直安慰自己柱彻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布餐胀。 她就那樣靜靜地躺著哟楷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪否灾。 梳的紋絲不亂的頭發(fā)上卖擅,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音,去河邊找鬼惩阶。 笑死挎狸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的断楷。 我是一名探鬼主播锨匆,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冬筒!你這毒婦竟也來了恐锣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤舞痰,失蹤者是張志新(化名)和其女友劉穎土榴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體响牛,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡玷禽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了呀打。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矢赁。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖聚磺,靈堂內(nèi)的尸體忽然破棺而出坯台,到底是詐尸還是另有隱情炬丸,我是刑警寧澤瘫寝,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站稠炬,受9級特大地震影響焕阿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜首启,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一暮屡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧毅桃,春花似錦褒纲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至读宙,卻和暖如春彻秆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工唇兑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酒朵,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓扎附,卻偏偏與公主長得像蔫耽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子留夜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容