iOS 效能優化總結

LaiYoung_發表於2018-04-11

卡頓產生的原因

iOS 效能優化總結

VSync 訊號到來後,系統圖形服務會通過 CADisplayLink 等機制通知 AppApp 主執行緒開始在 CPU 中計算顯示內容,比如檢視的建立、佈局計算、圖片解碼、文字繪製等。隨後 CPU 會將計算好的內容提交到 GPU 去,由 GPU 進行變換、合成、渲染。隨後 GPU 會把渲染結果提交到幀緩衝區去,等待下一次 VSync 訊號到來時顯示到螢幕上。由於垂直同步的機制,如果在一個 VSync 時間內,CPU 或者 GPU 沒有完成內容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時螢幕會保留之前的內容不變。這就是介面卡頓的原因。

在開發中,CPUGPU中任何一個壓力過大,都會導致掉幀現象,所以在開發時,也需要分別對CPUGPU壓力進行評估和優化。

iOS 裝置中的 CPU & GPU

CPU

載入資源,物件建立,物件調整,物件銷燬,佈局計算,Autolayout,文字計算,文字渲染,圖片的解碼, 影象的繪製(Core Graphics)都是在CPU上面進行的。

GPU

GPU是一個專門為圖形高併發計算而量身定做的處理單元,比CPU使用更少的電來完成工作並且GPU的浮點計算能力要超出CPU很多。

GPU的渲染效能要比CPU高效很多,同時對系統的負載和消耗也更低一些,所以在開發中,我們應該儘量讓CPU負責主執行緒的UI調動,把圖形顯示相關的工作交給GPU來處理,當涉及到光柵化等一些工作時,CPU也會參與進來,這點在後面再詳細描述。

相對於CPU來說,GPU能幹的事情比較單一:接收提交的紋理(Texture)和頂點描述(三角形),應用變換(transform)、混合(合成)並渲染,然後輸出到螢幕上。通常你所能看到的內容,主要也就是紋理(圖片)和形狀(三角模擬的向量圖形)兩類。

CPU 和 GPU 的協作

iOS 效能優化總結

由上圖可知,要在螢幕上顯示檢視,需要CPUGPU一起協作,CPU計算好顯示的內容提交到GPUGPU渲染完成後將結果放到幀快取區,隨後視訊控制器會按照 VSync 訊號逐行讀取幀緩衝區的資料,經過可能的數模轉換傳遞給顯示器顯示。

緩衝機制

iOS 效能優化總結

iOS使用的是雙緩衝機制。即GPU會預先渲染好一幀放入一個緩衝區內(前幀快取),讓視訊控制器讀取,當下一幀渲染好後,GPU會直接把視訊控制器的指標指向第二個緩衝器(後幀快取)。當你視訊控制器已經讀完一幀,準備讀下一幀的時候,GPU會等待顯示器的VSync訊號發出後,前幀快取和後幀快取會瞬間切換,後幀快取會變成新的前幀快取,同時舊的前幀快取會變成新的後幀快取。

優化方案

YY大神的 iOS 保持介面流暢的技巧中詳細介紹了 CPU 資源消耗原因和解決方案GPU 資源消耗原因和解決方案,這裡麵包括了開發中的大部分場景,可以幫助我們快速定位卡頓的原因,迅速解決卡頓。

下面是一些常見的優化方案!

TableViewCell 複用

cellForRowAtIndexPath:回撥的時候只建立例項,快速返回cell,不繫結資料。在willDisplayCell: forRowAtIndexPath:的時候繫結資料(賦值)。

高度快取

tableView滑動時,會不斷呼叫heightForRowAtIndexPath:,當 cell 高度需要自適應時,每次回撥都要計算高度,會導致 UI 卡頓。為了避免重複無意義的計算,需要快取高度。

怎麼快取?

檢視層級優化

不要動態建立檢視
  • 在記憶體可控的前提下,快取subview
  • 善用hidden
減少檢視層級
  • 減少subviews個數,用layer繪製元素。
  • 少用 clearColormaskToBounds,陰影效果等。
減少多餘的繪製操作

圖片

  • 不要用JPEG的圖片,應當使用PNG圖片。
  • 子執行緒預解碼(Decode),主執行緒直接渲染。因為當image沒有Decode,直接賦值給imageView會進行一個Decode操作。
  • 優化圖片大小,儘量不要動態縮放(contentMode)。
  • 儘可能將多張圖片合成為一張進行顯示。

減少透明 view

使用透明view會引起blending,在iOS的圖形處理中,blending主要指的是混合畫素顏色的計算。最直觀的例子就是,我們把兩個圖層疊加在一起,如果第一個圖層的透明的,則最終畫素的顏色計算需要將第二個圖層也考慮進來。這一過程即為Blending

會導致blending的原因:

  • UIViewalpha < 1
  • UIImageViewimage含有alpha channel(即使UIImageViewalpha1,但只要image含有透明通道,則仍會導致blending)。

為什麼blending會導致效能的損失?

原因是很直觀的,如果一個圖層是不透明的,則系統直接顯示該圖層的顏色即可。而如果圖層是透明的,則會引起更多的計算,因為需要把另一個的圖層也包括進來,進行混合後的顏色計算。

  • opaque設定為YES,減少效能消耗,因為GPU將不會做任何合成,而是簡單從這個層拷貝。

減少離屏渲染

離屏渲染指的是在影象在繪製到當前螢幕前,需要先進行一次渲染,之後才繪製到當前螢幕。

OpenGL中,GPU螢幕渲染有以下兩種方式:

  • On-Screen Rendering即當前螢幕渲染,指的是GPU的渲染操作是在當前用於顯示的螢幕緩衝區中進行。

  • Off-Screen Rendering即離屏渲染,指的是GPU在當前螢幕緩衝區以外新開闢一個緩衝區進行渲染操作。

為什麼離屏渲染會發生卡頓?主要包括兩方面內容:

  • 建立新的緩衝區。
  • 上下文切換,離屏渲染的整個過程,需要多次切換上下文環境(CPU渲染和GPU切換),先是從當前螢幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束以後,將離屏緩衝區的渲染結果顯示到螢幕上又需要將上下文環境從離屏切換到當前螢幕。而上下文環境的切換是要付出很大代價的。

設定了以下屬性時,都會觸發離屏渲染:

  • layer.shouldRasterize,光柵化

  • layer.mask,遮罩

  • layer.allowsGroupOpacityYESlayer.opacity的值小於1.0

  • layer.cornerRadius,並且設定layer.masksToBoundsYES。可以使用剪下過的圖片,或者使用layer畫來解決。

  • layer.shadows,(表示相關的shadow開頭的屬性),使用shadowPath代替。

    兩種不同方式來繪製陰影: 不使用shadowPath

    iOS 效能優化總結

    使用shadowPath

    iOS 效能優化總結

    效能差別,如下圖:

    iOS 效能優化總結

離屏渲染的優化建議

  • 使用ShadowPath指定layer陰影效果路徑。
  • 使用非同步進行layer渲染(Facebook開源的非同步繪製框架AsyncDisplayKit)。
  • 設定layeropaque值為YES,減少複雜圖層合成。
  • 儘量使用不包含透明(alpha)通道的圖片資源。
  • 儘量設定layer的大小值為整形值。
  • 直接讓美工把圖片切成圓角進行顯示,這是效率最高的一種方案。
  • 很多情況下使用者上傳圖片進行顯示,可以在客戶端處理圓角。
  • 使用程式碼手動生成圓角image設定到要顯示的View上,利用UIBezierPathCore Graphics框架)畫出來圓角圖片。

合理使用光柵化 shouldRasterize

光柵化是把GPU的操作轉到CPU上,生成點陣圖快取,直接讀取複用。

優點:
  • CALayer會被光柵化為bitmapshadowscornerRadius等效果會被快取。
缺點:
  • 更新已經光柵化的layer,會造成離屏渲染。
  • bitmap超過100ms沒有使用就會移除。
  • 受系統限制,快取的大小為 2.5X Screen Size。

shouldRasterize 適合靜態頁面顯示,動態頁面會增加開銷。如果設定了shouldRasterizeYES,那也要記住設定rasterizationScalecontentsScale

非同步渲染

在子執行緒繪製,主執行緒渲染。例如 VVeboTableViewDemo

iOS 效能優化總結

理性使用-drawRect:

大家或許感到奇怪,有不少開發者在發有關效能優化的部落格當中指出使用-drawRect:來優化效能。但是我這裡不太建議大家未經思考的使用-drawRect:方法。原因如下:

當你使用UIImageView在載入一個檢視的時候,這個檢視雖然依然有CALayer,但是卻沒有申請到一個後備的儲存,取而代之的是使用一個使用螢幕外渲染,將CGImageRef作為內容,並用渲染服務將圖片資料繪製到幀的緩衝區,就是顯示到螢幕上,當我們滾動檢視的時候,這個檢視將會重新載入,浪費效能。所以對於使用-drawRect:方法,更傾向於使用CALayer來繪製圖層。因為使用CALayer-drawInContext:Core Animation將會為這個圖層申請一個後備儲存,用來儲存那些方法繪製進來的點陣圖。那些方法內的程式碼將會執行在 CPU上,結果將會被上傳到GPU。這樣做的效能更為好些。

靜態介面建議使用-drawRect:的方式,動態頁面不建議。

按需載入

  • 區域性重新整理,重新整理一個cell就能解決的,堅決不重新整理整個 section 或者整個tableView重新整理最小單元元素
  • 利用runloop提高滑動流暢性,在滑動停止的時候再載入內容,像那種一閃而過的(快速滑動),就沒有必要載入,可以使用預設的佔位符填充內容。

關於效能測試

在出現影象效能問題,滑動,動畫不夠流暢之後,我們首先要做的就是定位出問題的所在。而這個過程並不是只靠經驗和窮舉法探索,我們應該用有脈絡,有順序的科學的手段進行探索。

首先,我們要有一個定位問題的模式。我們可以按照這樣的順序來逐步定位,發現問題。

  1. 定位幀率,為了給使用者流暢的感受,我們需要保持幀率在60幀左右。當遇到問題後,我們首先檢查一下幀率是否保持在60幀。
  2. 定位瓶頸,究竟是CPU還是GPU。我們希望佔用率越少越好,一是為了流暢性,二也節省了電力。
  3. 檢查有沒有做無必要的CPU渲染,例如有些地方我們重寫了drawRect:,而其實是我們不需要也不應該的。我們希望GPU負責更多的工作。
  4. 檢查有沒有過多的離屏渲染,這會耗費GPU的資源,像前面已經分析的到的。離屏渲染會導致GPU需要不斷地onScreenoffscreen進行上下文切換。我們希望有更少的離屏渲染。
  5. 檢查我們有無過多的BlendingGPU渲染一個不透明的圖層更省資源。
  6. 檢查圖片的格式是否為常用格式,大小是否正常。如果一個圖片格式不被GPU所支援,則只能通過CPU來渲染。一般我們在iOS開發中都應該用PNG格式,之前閱讀過的一些資料也有指出蘋果特意為PNG格式做了渲染和壓縮演算法上的優化。
  7. 檢查是否有耗費資源多的View或效果,我們需要合理有節制的使用。
  8. 最後,我們需要檢查在我們View層級中是否有不正確的地方。例如有時我們不斷的新增或移除View,有時就會在不經意間導致bug的發生。
測試工具:
  • Core AnimationInstruments裡的圖形效能問題的測試工具。
  • view debugging,Xcode 自帶的,檢視層級。
  • reveal,檢視層級。

參考文章

如有內容錯誤,歡迎 issue 指正。

轉載請註明出處!

相關文章