UIKit性能調優實戰講解

2021-03-02 聽雲

在使用UIKit的過程中,性能優化是永恆的話題。很多人都看過分析優化滑動性能的文章,但其中不少文章只介紹了優化方法卻對背後的原理避而不談,或者是晦澀難懂而且讀者缺乏實踐體驗的機會。不妨思考一下下面的問題自己是否有一個清晰的認識:

1、為什麼要把控制項儘量設置成不透明的,如果是透明的會有什麼影響,如何檢測這種影響?

2、為什麼cell中的圖片,儘可能要使用正確的大小、格式,如果錯誤會有什麼影響,如何檢測這種影響?

3、為什麼設置陰影和圓角有可能影響滑動時流暢度?

4、shouldRasterize和離屏渲染的關係是什麼,何時應該使用?

本文會結合Instrument分析影響性能的因素,提出優化方案並解釋背後的原理,項目初始demo的下載地址在我的Github:https://github.com/bestswifter/MySampleCode/tree/master/GraphicsPerformance-Starter ,強烈建議每一位讀者下載下來隨著我一步一步調試、優化。如果覺得對自己有幫助,可以給一個Star表示支持。後面的圖片較多,流量黨慎入。

基本概念

打開項目後,只要CustomTableCell.swift文件即可,它實現了自定義的UITableViewCell以及內部的UI布局,因為重點在於性能優化,代碼實現的就比較隨意。

首先按下Command + I打開Instrument,本文主要用到的是Core Animation工具:


打開Core Animation調試

注意這個調試必須使用真機,點擊左上角的紅色圓圈就會開始錄製。新手可能不太熟悉,這裡簡單介紹一下調試界面:


我們需要了解兩個兩個區域:

1、這裡記錄了實時的fps數值,有些地方是0是因為屏幕沒有滑動

2、這是重中之重,接下來我會帶大家逐個理解、體驗這些調試選項

有過遊戲經驗的人也許對fps這個概念比較熟悉。我們知道任何屏幕總是有一個刷新率,比如iphone推薦的刷新率是60Hz,也就是說GPU每秒鐘刷新屏幕60次,因此兩次刷新之間的間隔為16.67ms。這段時間內屏幕內容保持不變,稱為一幀(frame),fps表示frames per second,也就是每秒鐘顯示多少幀畫面。對於靜止不變的內容,我們不需要考慮它的刷新率,但在執行動畫或滑動時,fps的值直接反映出滑動的流暢程度。

調試、優化

圖層混合

首先我們要明白像素的概念,屏幕上每一個點都是一個像素,像素有R、G、B三種顏色構成(有時候還帶有alpha值)。如果某一塊區域上覆蓋了多個layer,最後的顯示效果受到這些layer的共同影響。舉個例子,上層是藍色(RGB=0,0,1),透明度為50%,下層是紅色(RGB=1,0,0)。那麼最終的顯示效果是紫色(RGB=0.5,0,0.5)。這種顏色的混合(blending)需要消耗一定的GPU資源,因為實際上可能不止只有兩層。如果只想顯示最上層的藍色,可以把它的透明度設置為100%,這樣GPU會忽略下面所有的layer,從而節約了很多不必要的運算。

第一個調試選項"Color Blended Layers"正是用於檢測哪裡發生了圖層混合,並用紅色標記出來。因此我們需要儘可能減少看到的紅色區域。一旦發現應該想法設法消除它。開始調試後勾選這個選項,我們在手機上可以看到如下的場景:


Color Blended Layers

很多文章裡說把控制項設置為opaque = true,其原理就是希望避免圖層混合,然而這種調優一般情況下用處不大。因為UIView的opaque屬性默認值就是true,也就是說只要不是人為設置成透明,都不會出現圖層混合。比如demo中就沒有任何透明的控制項。

對於UIImageView來說,不僅它自身需要是不透明的,它的圖片也不能含有alpha通道,這就是為什麼圖中第三個圖片是綠色,而前兩個圖片是紅色的原因。由於本人對PS和圖像幾乎一竅不通,恕我不能演示如何消除這些圖片的紅色。我從網上找了一個美女的頭像來說明,圖像自身的性質可能會對結果有影響,因此如果你確定自己的代碼沒有問題,而且出現了圖層混合,請聯繫美工或後臺解決。

個人認為比opaque屬性更重要的是backgroundColor屬性,如果不設置這個屬性,控制項依然被認為是透明的,所以我們做的第一個優化是在CustomTableCell類的init方法中添加一行代碼:


雖然在白色背景下,這行代碼無法肉眼看到效果,但重新調試後我們可以發現label的紅色消失了。也正是因為對背景顏色的不重視,它成了影響滑動性能的第一個殺手。

PS:如果label文字有中文,依然會出現圖層混合,這是因為此時label多了一個sublayer,如果有好的解決辦法歡迎告訴我。

光柵化

光柵化是將一個layer預先渲染成位圖(bitmap),然後加入緩存中。如果對於陰影效果這樣比較消耗資源的靜態內容進行緩存,可以得到一定幅度的性能提升。demo中的這一行代碼表示將label的layer光柵化:


Instrument中,第二個調試選項是「Color Hits Green and Misses Red」,它表示如果命中緩存則顯示為綠色,否則顯示為紅色,顯然綠色越多越好,紅色越少越好。勾選這個選項後我們看到如下的場景:


color Hits Green and Misses Red

光柵化的核心在於緩存的思想。我們自己動手把玩一下,可以發現以下幾個有意思的現象:

1、上下微小幅度滑動時,一直是綠色

2、上下較大幅度滑動,新出現的label一開始是紅色,隨後變成綠色

3、如果靜止一秒鐘,剛開始滑動時會變紅。

這是因為layer進行光柵化後渲染成位圖放在緩存中。當屏幕出現滑動時,我們直接從緩存中讀取而不必渲染,所以會看到綠色。當新的label出現時,緩存中沒有個這個label的位圖,所以會變成紅色。第三點比較關鍵,緩存中的對象有效期只有100ms,即如果在0.1s內沒有被使用就會自動從緩存中清理出去。這就是為什麼停留一會兒再滑動就會看到紅色。

光柵化的緩存機制是一把雙刃劍,先寫入緩存再讀取有可能消耗較多的時間。因此光柵化僅適用於較複雜的、靜態的效果。通過Instrument的調試發現,這裡使用光柵化經常出現未命中緩存的情況,如果沒有特殊需要則可以關閉光柵化,所以我們做的第二個優化是注釋掉下面這行代碼:


光柵化會導致離屏渲染,這一點待會兒會講。

顏色格式

像素在內存中的布局和它在磁碟中的存儲方式並不相同。考慮一種簡單的情況:每個像素有R、G、B和alpha四個值,每個值佔用1位元組,因此每個像素佔用4位元組的內存空間。一張1920*1080的照片(iPhone6 Plus的解析度)一共有2,073,600個像素,因此佔用了超過8Mb的內存。但是一張同樣解析度的PNG格式或JPEG格式的圖片一般情況下不會有這麼大。這是因為JPEG將像素數據進行了一種非常複雜且可逆的轉化。

當我們打開JPEG格式的圖片時,CPU會進行一系列運算,將JPEG圖片解壓成像素數據。顯然這個工作會消耗不少時間,所以不應該在滑動時進行,我們應該預先處理好圖片。借用WWDC上的一頁PPT來說明:


顯示流程

Commit Transaction和Decode在同一幀內進行,如果這兩個操作的耗時超過16.67s,Draw Calls就會延遲到下一幀,從而導致fps值的降低。下面是Commit Transaction的詳細流程:


解碼與轉換

在第三步的Prepare中,CPU主要處理兩件事:

1、把圖片從PNG或JPEG等格式中解壓出來,得到像素數據

2、如果GPU不支持這種顏色各式,CPU需要進行格式轉換

比如應用中有一些從網絡下載的圖片,而GPU恰好不支持這個格式,這就需要CPU預先進行格式轉化。第三個選項「Color Copied Images」就用來檢測這種實時的格式轉化,如果有則會將圖片標記為藍色。

遺憾的是由於我對圖片格式不太了解,也不會使用相關工具,並沒有能模擬出觸發這個選項的場景。我們要記住的是,如果調試時發現有圖片被標記為藍色,說明圖片格式出現了一些問題。

圖片大小

第四個選項的使用場景不多,我們直接看一下第五個選項「Color Misaligned Images」。它表示如果圖片需要縮放則標記為黃色,如果沒有像素對齊則標記為紫色。勾選上這個選項並進行調試,可以看到如下場景:


在demo中,每個UIImageView的大小都是180x180,而只有第二張圖片的像素大小是360x360。因此除了第二張圖片,其他的圖片都需要被縮放。圖片的縮放需要佔用時間,因此我們要儘可能保證無論是本地圖片還是從網絡或取得圖片的大小,都與其frame保持一致。

第三個優化是調整所有圖片的像素大小以避免不必要的縮放。

離屏渲染


離屏渲染表示渲染發生在屏幕之外,你可能認為這是一句廢話。為了真正解釋清楚什麼是離屏渲染,我們先來看一下正常的渲染通道(Render-Pass):


正常渲染通道

首先,OpenGL提交一個命令到Command Buffer,隨後GPU開始渲染,渲染結果放到Render Buffer中,這是正常的渲染流程。但是有一些複雜的效果無法直接渲染出結果,它需要分步渲染最後再組合起來,比如添加一個蒙版(mask):


離屏渲染

在前兩個渲染通道中,GPU分別得到了紋理(texture,也就是那個相機圖標)和layer(藍色的蒙版)的渲染結果。但這兩個渲染結果沒有直接放入Render Buffer中,也就表示這是離屏渲染。直到第三個渲染通道,才把兩者組合起來放入Render Buffer中。離屏渲染意味著把渲染結果臨時保存,等用到時再取出,因此相對於普通渲染更佔用資源。

第六個選項「Color Offscreen-Rendered Yellow」會把需要離屏渲染的地方標記為黃色,大部分情況下我們需要儘可能避免黃色的出現。離屏渲染可能會自動觸發,也可以手動觸發。以下情況可能會導致觸發離屏渲染:


前兩者會自動觸發離屏渲染,第三種方法是手動開啟離屏渲染。

開始調試並勾選「Color Offscreen-Rendered Yellow」,會看到這樣的場景:


離屏渲染

如果沒有進行第二步優化,你會發現label也是黃色。可以看到tabbar和statusBar也是黃色,這是因為它們使用了模糊效果。圖片也是黃色,這說明它也進行了離屏渲染,觀察源碼後發現主要原因是它使用了陰影,接下來我們進行第四個優化,在設置陰影效果的四行代碼下面添加一行:


這行代碼制定了陰影路徑,如果沒有手動指定,Core Animation會去自動計算,這就會觸發離屏渲染。如果人為指定了陰影路徑,就可以免去計算,從而避免產生離屏渲染。

設置cornerRadius本身並不會導致離屏渲染,但很多時候它還需要配合layer.masksToBounds = true使用。根據之前的總結,設置masksToBounds會導致離屏渲染。解決方案是儘可能在滑動時避免設置圓角,如果必須設置圓角,可以使用光柵化技術將圓角緩存起來:


快速路徑

還記得之前將離屏渲染和渲染路徑時的示意圖麼,離屏渲染的最後一步是把此前的多個路徑組合起來。如果這個組合過程能由CPU完成,就會大量減少GPU的工作。這種技術在繪製地圖中可能用到。

第七個選項「Color Compositing Fast-Path Blue」用於標記由硬體繪製的路徑,藍色越多越好。

變化區域

刷新視圖時,我們應該把需要重繪的區域儘可能縮小。對於未發生變化的內容則不應該重繪,第八個選項「Flash updated Regions」用於標記發生重繪的區域。一個典型的例子是系統的時鐘應用,絕大多數時候只有顯示秒針的區域需要重繪:


總結

如果你一步一步做到了這裡,我想一定會有不少收益。不過,學而不思則罔,思而不學則殆。動手實踐後還是應該總結提煉,優化滑動性能主要涉及三個方面:

避免圖層混合

1、確保控制項的opaque屬性設置為true,確保backgroundColor和父視圖顏色一致且不透明

2、如無特殊需要,不要設置低於1的alpha值

3、確保UIImage沒有alpha通道

避免臨時轉換

1、確保圖片大小和frame一致,不要在滑動時縮放圖片

2、確保圖片顏色格式被GPU支持,避免勞煩CPU轉換

慎用離屏渲染

1、絕大多數時候離屏渲染會影響性能

2、重寫drawRect方法,設置圓角、陰影、模糊效果,光柵化都會導致離屏渲染

3、設置陰影效果是加上陰影路徑

4、滑動時若需要圓角效果,開啟光柵化

文章轉載自簡書—《UIKit性能調優實戰講解》 http://www.jianshu.com/p/619cf14640f3

相關焦點

  • GC算法 + 垃圾收集器 + 性能調優實戰
    目錄GC算法GC收集器GC調優實戰GC常用參數GC算法標記-清除算法它是最基礎的收集算法,這個算法分為兩個階段,「標記」和」清除「。首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象。它有兩個不足的地方:1. 效率問題,標記和清除兩個過程的效率都不高;2.
  • 譯文|RedHat 性能調優
    如果過早實施,或者沒有性能評估,性能性能優化可能,或者必須導致相反的效果。但是如果系統的進行,性能優化可以是一門科學,一種藝術。方法首先搞清楚什麼是「正常」情況。尋找潛在的性能問題,調整性能參數修復問題,調整後監控系統性能,以決定是保留修改還是回退。總結:收集數據建立基線 啟用優化,選擇合理默認值查看圖表,我們的優化是不是正確選擇?
  • JVM性能調優實踐
    點擊上方藍色字體,選擇「標星公眾號」優質文章,第一時間送達  作者 |  滴哩哩哩滴哩哩哩噠噠來源 |  urlify.cn/mqMVvu76套java從入門到精通實戰課程分享1、JVM調優目標:使用較小的內存佔用來獲得較高的吞吐量或者較低的延遲。
  • SQL性能調優:查找慢查詢的5種方法
    SQL性能調優是一場永無止盡的鬥爭。我不是一名DBA,而是一名15年的開發人員。我從開始用SQL Server資料庫工作,一直到用Stackify的大SQL Azure資料庫。這麼多年來,我看到了一切。在本文中,我將提供一些技巧,以便開發人員可以在SQL Server中查找SQL慢查詢並進行性能調優。
  • etcd 性能測試與調優
    快照調優為 v2 後端存儲創建快照的代價是很高的,所以只用當參數累積到一定的數量時,Etcd 才會創建快照文件。默認情況下,修改數量達到 10000 時才會建立快照。如果 Etcd 的內存使用和磁碟使用過高,那麼應該嘗試調低快照觸發的閾值,具體請參考如下命令。
  • 如何快速掌握Java的性能調優技巧
    如何快速掌握Java的性能調優技巧 zhisheng的blog 發表於 2020-02-22 15:44:35 大多數開發者認為性能優化是一個複雜的話題,它需要大量的工作經驗和相關知識理論
  • 大型企業JVM性能調優實戰Java垃圾收集器及gcroot
    02:JVM類加載機制大型企業JVM性能調優實戰之gcroot01:JVM類加載機制 - 類加載的生命周期概述java的class類文件實際上是二進位(字節碼)文件格式,class文件中包含了java虛擬機指令集和符號表以及若干其他輔助信息。
  • Cloudera Hadoop管理認證實戰
    Cloudera大學為期4天的Hadoop管理員培訓將幫助學員綜合理解並全面掌握通過Cloudera Manager對Hadoop機群的運維管理,包括:安裝,配置、 負載平衡及性能調優等。通過該培訓,Hadoop 系統管理員將能準備好應對實 際運維中遇到的挑戰。
  • DBA必讀:資料庫性能調優的五大絕招
    【IT168 技術】當資料庫的性能變得糟糕時,IT人員能扭轉局面。  生產資料庫的性能會隨著業務和數據的增長而嚴重遲鈍。每一個關鍵資料庫性能的降低都可能會導致全面的業務損失。  從技術上講,性能優化可以從許多不同的層次下手——應用程式可以優化,資料庫可以調整,或建立新的系統架構。
  • HBase調優|HBase + G1GC 性能調優
    先傳送門一下,之前在HBaseConAsia2017分享過一個G1GC調優的PPT: http://openinx.github.io/2012/01/01/my-share/首先,對G1算法不熟悉的同學,可以仔細讀一讀Oracle的G1算法教程,教程基本交代了G1的運行原理以及和CMS本質區別,如果對算法細節感興趣,可以讀一下Garbage-First
  • 關於GC原理和性能調優,看這一篇就夠了!
    GC 調優目標大多數情況下對 Java 程序進行 GC 調優,主要關注兩個目標:響應速度(Responsiveness):響應速度指程序或系統對一個請求的響應有多迅速比如,用戶訂單查詢響應時間,對響應速度要求很高的系統,較大的停頓時間是不可接受的。調優的重點是在短的時間內快速響應。
  • GC調優在Spark應用中的實踐
    並且同時,它也支持兼容批處理和流式處理,對於程序吞吐量和延遲都有較高要求,因此GC參數的調優在Spark應用實踐中顯得尤為重要。本文主要講述如何針對Spark應用程式配置JVM的垃圾回收器,並從實際案例出發,剖析如何進行GC調優,進一步提升Spark應用的性能。
  • Tomcat 調優的技巧 | 必學必知
    一、描述最近,在補充自己的短板,剛好整理到 Tomcat 調優這塊,基本上面試必問,於是就花了點時間去搜集一下 Tomcat 調優都調了些什麼,先記錄一下調優手段,更多詳細的原理和實現以後用到時候再來補充記錄,下面就來介紹一下,Tomcat 調優大致分為兩大類。
  • 啃了三個月這本JVM實戰和Java核心知識點,終於從外包闖進了華為
    可以說,Java虛擬機就是每一位Java工程師進階加薪的利器,你想往上升,你想深入技術,不想一直停留在簡單開發,或者你在做Java性能分析、調優工作時,那麼,Java虛擬機絕對是一把助力的利劍。一起看看這本《JVM實戰》本書一共分為五個部分:走近Java.自動內存管理機制、虛擬機執行子系統、程序編譯與代碼優化、高效並發。各個部分基本上是相互獨立的,沒有必然的前後依賴關係,讀者可以從任何一個感興趣的專題開始閱讀,但是每個部分中的各個章節間有先後順序。第一部分走近Java本書的第一部分為後文的講解建立了良好的基礎。
  • 通向架構師的道路(第四天)之 Tomcat 性能調優
    ,我們得知了決定性能測試的幾個重要指標,它們是:吞吐量ResponsetimeCpuloadMemoryUsage我們也在第三天的學習中對Apache做過了一定的優化,使其最優化上述4大核心指標的讀數,那麼我們的Apache調優了,我們的Tomcat也作些相應的調整,當完成今的課程後,到時你的「小貓」到時真的會「飛」起來的,所以請用心看完
  • 實用:9 個可以快速掌握的 Java 性能調優技巧
    優化一個應用做到性能最優化可能不是件容易的任務,但是這並不意味著你沒有相關的知識就什麼也做不了。這裡有一些易於遵循的建議和最佳實踐可以幫助你創建一個性能良好的應用程式。這些建議的大部分都是針對 Java 語言的。但是也有一些是跟語言無關的,你可以運用到任意的應用和程序中。在我們學習特定的 Java 編程性能調優之前,先來探討一些通用的技巧。1.
  • 實用,9 個可以快速掌握的 Java 性能調優技巧
    優化一個應用做到性能最優化可能不是件容易的任務,但是這並不意味著你沒有相關的知識就什麼也做不了。這裡有一些易於遵循的建議和最佳實踐可以幫助你創建一個性能良好的應用程式。這些建議的大部分都是針對 Java 語言的。但是也有一些是跟語言無關的,你可以運用到任意的應用和程序中。在我們學習特定的 Java 編程性能調優之前,先來探討一些通用的技巧。
  • JAVA JVM調優實戰:G1中的to-space exhausted問題
    針對上面的問題,我們最終確定了下面的調優建議:這次沒有發生FGC,可能是由於我前面將xmx和xms調大了導致的,這次準備將xmx和xms先調回到原來的值;加上HeapDumpAfterFullGC參數,下次再發生類似情況的時候,就會觸發FGC,然後自動
  • Java 性能調優的 11 個實用技巧
    大多數開發人員理所當然地以為性能優化很複雜,需要大量的經驗和知識。好吧,不能說這是完全錯誤的。優化應用程式以獲得最佳性能不是一件容易的事情。但是,這並不意味著如果你不具備這些知識,就不能做任何事情。這裡有11個易於遵循的建議和最佳實踐可以幫助你創建一個性能良好的應用程式。大部分建議是針對Java的。但也有若干建議是與語言無關的,可以應用於所有應用程式和程式語言。
  • Machine Learning-模型調優備忘錄
    這裡有一份關於模型調優的備忘錄,每當出現上述問題的時候或者是建模前,都可以按照這個清單來進行檢查,話不多說,大家一起來看看唄~(原文來自Jason Brownlee 的文章《Machine Learning Performance Improvement Cheat Sheet》)