如何持續突破效能表現?DX 效能優化策略詳解
DX全稱DinamicX,目前是在淘寶乃至整個阿里集團內廣泛使用的Native動態化方案,核心優勢是效能和穩定性。過去幾年一直有其他淘寶/集團的外部文章中有涉及到DX,但DX一直沒有對外做過完整介紹,對外界來說這兩個字母頗有些神祕色彩。本系列文章《DX研發模式》我們就將拉下它神祕的面紗,看看過去兩年 DX 在做什麼。
本文主要闡述 DX 在追求極致的效能體驗過程中,所突破的效能瓶頸與實踐經驗。
《DX研發模式》系列文章預告如下,歡迎持續關注:
-
本文: 《如何持續突破效能表現?》
-
6月2日:《從0到1,IDE如何提升端側研發效率?》
前言
DX 作為一種邏輯和檢視分離的跨平臺動態化方案,是集團內高效能動態化框架的代表,其效能是最接近 Native 的。根據業務的側重點不同,技術選型也不同,沒有絕對的最優解。在追求高效能極致體驗,高穩定性及能夠提供部分邏輯動態化的場景下,DX 為當前的第一選擇。
現狀
-
DX 為了追求極致的效能體驗:
-
在服務端編譯期就將 XML 檔案解析成二進位制,將生成抽象語法樹的過程前置到編譯期執行,端上執行時,僅需要拿到二進位制檔案進行屬性解析,對於靜態值型別,在編譯期就直接轉換成了對應的資料型別,減少了端上的拆裝箱開銷;
-
程式碼均使用平臺原生語言實現,避免了各種跨語言通訊成本;
-
DX 通過使用輕量、不可變的虛擬樹節點進行測量、佈局等前置操作,只在最後 render 階段才會操作平臺檢視。 在渲染 view 之前會再次進行檢視 diff 操作,確保儘可能複用 view,只生成必要的 view;
-
DX在過去的一年通過事件鏈能力擴充套件了部分邏輯動態化能力,但這些能力屬於可插拔能力,並不參與 DX 檢視渲染階段,以上這些都是 DX 持續保持高效能的基礎;
-
通過對 DX 的管線分析,我們發現 DX 的效能還有進一步提升空間,比如:
-
單執行緒的管線設計,在較為複雜的模板場景下,DX的虛擬樹操作階段耗時不可忽略;
-
依託平臺本身的繪製能力限制,在大量文字、圖片等場景下,系統實現均在主執行緒進行繪製,在手淘這種大量圖文的場景下,平臺本身的繪製效能限制也不可忽略;
-
由於部分業務高頻元件缺乏內建通用實現,在龐大的業務方自定義元件生態中,效能、穩定性、通用性均無法得到保證。
基於上述分析流程,當前 DX 面臨的效能瓶頸主要分為兩個部分:虛擬節點樹和檢視渲染。
-
針對渲染管線的虛擬節點操作階段,我們提供了管線非同步化能力。
-
針對渲染階段,我們提供了非同步繪製框架、通用富文字元件及部分屬效能力優化。
-
針對整體 CPU 計算資源佔用,我們提供了離屏資源管控框架
管線優化
由於 DX 的服務端預編譯及虛擬節點輕量等特徵,一般場景下,虛擬節點的主執行緒耗時佔比並不高,但在業務模板較為複雜時,比如含有大量表達式和複雜巢狀層級場景下,虛擬節點操作耗時佔比可能超過 40%,在由 DX 卡片搭建的全頁面場景下,虛擬節點的主執行緒耗時佔比甚至超過 10%,所以虛擬節點耗時亟需優化。
一、管線簡介
在 DX 中,開發者在 XML 中寫的元件到端上都會先解析為“三棵樹”後再交由真正的平臺 view 進行渲染。分別為:原型樹、展開樹、拍平樹。渲染管線會在虛擬節點樹上分別將二進位制解析、表示式解析、測量、佈局、拍平等步驟進行操作,步驟之間可拆分重組,這也就為管線非同步化打下了基礎。
-
原型樹:從二進位制下發後到端上的原始樹結構,主要包含靜態樹節點賦值及單位換算;
-
展開樹:根據初始的原型樹進行資料進一步解析,包含動態屬性的解析、layout 子節點的轉換、節點的測量及佈局等操作;
-
拍平樹:根據佈局好的展開樹進行再進一步的優化,主要包含無用佈局節點的拍平以及整體層級的拍平,拍平樹是真正需要交由平臺 view 進行渲染的最終狀態。
二、管線非同步化
而從前面的流程分析我們可以瞭解到,DX 的虛擬節點並不操作實際平臺 view,那麼是否可以充分利用 CPU 的多核能力進行多執行緒管線排程,將非必要佔用主執行緒的工作都非同步化執行?基於上述分析,我們設計了 DX 的管線非同步化能力。
管線非同步化整體思路簡化流程如圖:將虛擬節點上的載入、表示式解析、測量、佈局等操作均藉助多核 CPU 的能力非同步執行,僅有最後的拍平和渲染操作在主執行緒進行。
我們通過改造 DX 內部的渲染管線,提供了多種可擴充套件點,元件可將平臺渲染無關的耗時操作在 onPrefetch 非同步介面中提前執行,待到 render 階段時僅需執行 UI 相關流程即可。整體流程圖如下:
在 DX 內部,藉助管線非同步化能力,將圖片元件中與 UI 無關的操作 URLParser 步驟提前到 onPrefetch 回撥中提前進行,整體減少圖片庫 30% 以上時間佔比,在訂閱等圖片較多的業務中提升顯著。
由於 DX 豐富的生態中含有龐大的業務方自定義元件,所以將該能力作為 DX 基礎擴充套件介面,可由業務方在自定義元件中進行預載入時機的自定義操作處理,為自定義元件提供了一種優化策略。
上述主要介紹了管線非同步化的核心思路,針對外部容器和 DX 自建容器也提供了不同的接入策略:
-
DX 的外部業務接入方,通常都是將 DX 作為一張卡片,嵌入各種不同的自建容器中,針對這種場景,DX 提供了諸多不同的非同步化接入方式:單個/批量預載入能力,同步/非同步預載入能力,非同步渲染介面等,可以方便業務方根據自己的業務需求和業務場景進行選擇性接入。而在 DX 內部,為了避免和解決多執行緒濫用問題導致的執行緒頻繁切換開銷和執行緒爆炸等問題,我們設計了小型多執行緒管控佇列,管線非同步化也藉助該佇列,得以根據當前 CPU 核數等動態調整最大併發數;
-
針對外部容器雖然 DX 已提供了多種可選的非同步化接入介面,但對於業務方來說還是有一定接入成本的。而針對 DX 內部自建的功能強大的 RecycleLayout 容器,DX 提供了內建的管線非同步化能力,得以使用 RecycleLayout 的業務方可以通過 prefetch 屬性設定,無成本的接入管線非同步化。
渲染優化
利用管線非同步化方案,將虛擬節點上的操作非同步執行,大大減少了主執行緒壓力。但在不同業務場景下,虛擬節點操作和檢視渲染操作在主執行緒所佔比例並不盡相同,在大多數場景下更為耗時的為真正的檢視渲染階段。那麼是否可以充分利用系統多執行緒能力,將耗時的繪製操作放到非同步執行緒執行?基於此,我們設計並實現了非同步繪製框架以解決這種必須要在 CPU 上進行繪製時的主執行緒消耗,而自測自繪的富文字元件也成為了非同步繪製框架的第一個實際應用場景。
一、非同步繪製框架
系統 CALayer 通常有兩種繪製方式,直接使用紋理或手動繪製。DX 內部的非同步繪製框架簡易原理時序圖如下,在接收到系統 layer 的 display 訊息時,將紋理生成步驟在非同步執行緒執行完成後再回到主執行緒統一提交。
DX 基於系統的繪製流程,模仿系統實現,業務方可選擇直接實現點陣圖或是基於 DX 提供的畫布進行加工繪製。在該流程中間提供了有較多向外擴充套件點,滿足業務方不同的定製化和時機監控需求。並且由於系統繪製框架 CoreGraphics 為執行緒安全,天然為我們提供了非同步繪製的基礎,所以可以將繪製的步驟放到非同步執行緒進行。在提交任務時,通過監聽系統提交事務時間,將每個 runloop 中的繪製任務暫存,再統一在系統 commit 時機之後從主執行緒提交到 renderServer。
在 DX 的豐富生態中,不僅有大量的內建元件,還有極為龐大的開發者自定義元件。在 DX 體系下的技術改造如何不影響現有流程並且易於開發者自定義擴充套件是一個必須要考慮的問題。所以在設計非同步繪製框架時,採用面向介面程式設計的思想,對 DX 原有渲染邏輯無入侵性,也減輕了類之間的依賴耦合關係,可以實現僅對部分實現該協議的元件進行非同步繪製,擴充套件性較強,針對於每個步驟都有對應的擴充套件點用於開發者自定義操作。
內建 DXDisplayLayer 和 DXBaseView 作為非同步繪製基礎類,DXBaseView 作為 displayLayer 的檢視代理,用於檢視展示和手勢處理。DXWidgetNode 節點的 AsyncDisplay 分類作為 DisplayLayer 的 displayLayerDelegate,實現真正的同步/非同步自定義繪製能力排程。開發者在自定義元件中使用時,可以通過實現 DXWidgetNodeAsyncDisplayProtocol 介面即可接入非同步繪製,實現自己節點的非同步繪製能力。
對外暴露 DXDisplayLayer 和 DXBaseView 兩個基類,業務方可直接重寫自定義節點 view 的 layerClass ,或是直接繼承自 DXBaseView 來實現非同步繪製相關能力。
二、通用富文字能力
DX 之前並未提供統一的通用富文字能力,由各個業務方封裝自定義富文字元件。其中大部分都是為了完成業務方自身需求,具有較強的定製化屬性,無法做到通用性,並且效能也無法保證。而通用富文字能力,天然可作為非同步繪製框架在 DX 中的試驗場。
眾所周知,iOS 系統上的 UILabel 是在 CPU 上繪製成為一張 bitmap 後再交由 GPU 進行混合、合成等操作。而在大量圖文場景,例如手淘資訊流場景,含有大量價格標籤和各種角標,富文字需求強烈。在這種情況下,文字繪製整體佔比主執行緒 10% 以上。1、由於 UIKit 預設非執行緒安全,所以預設文字繪製均在主執行緒進行;2、由於 DX 渲染管線機制,需要先測量、佈局、再渲染,在測量的過程中需要藉助系統函式對文字測量,這也就導致了文字需要測量兩次,對主執行緒佔用較高,對幀率產生了較大負面影響。
1 iOS
在 iOS 上自定義繪製文字可選擇 TextKit / CoreText,從 iOS7 開始,蘋果提供了封裝性更加好的 TextKit 供開發者使用,並且把 UITextView、UILabel 等內建控制元件的佈局方式全部替換為 TextKit。
-
CoreText 的特點是可定製性強,靈活程度高、使用 C 語言,直接與 CoreGraphics 互動,執行緒安全。但其測量計算均需要自己實現,計算出來的寬高可能與系統控制元件有差別,程式碼維護困難;
-
而 TextKit 的特點是面向物件封裝性更強,API 均可在子執行緒方案,對上層開發者更加友好,在去年的 WWDC2021 中也進一步升級了 TextKit 能力。
基於以上特點以及 DX 富文字元件需求,並不需要特別複雜的佈局,需要滿足基礎的圖文混排及裁剪,我們選擇使用 TextKit 實現富文字元件。
通用富文字能力整體分層結構設計如下:
-
Basic層主要封裝非同步繪製的相關類,DisplayLayer及DXBaseView,其中主要由 DXWidgetNode 的 DXAsyncDisplay 分類提供非同步繪製的能力;
-
DXTextKit 層用於封裝系統TextKit中的NSLayoutManager、NSTextContainer及 NSTextStorage,用於實現圖文混排、自測自繪、點選長按等手勢、文字截斷等基礎能力;
-
WidgetNode層主要用於資訊解析和節點裝配;
-
最後真正的渲染層 RichTextView 作為繪製畫布進行 bitmap 繪製,並根據需要實現一些自定義屬性和能力。
2 Android
而在安卓上,Android 原生 TextView 其實際上底層都是基於 Layout 進行測量和繪製的。當傳入SpannableString 時,Layout(TextLine)會解析 SpannableString 中配置的各 Style,繪製前會使用特定的 Span 修改 Paint 屬性。DX 的富文字元件也是基於 SpannableString 方案,但並不直接使用 TextView,而是用底層的 Layout,View層上自己實現點選事件響應。一方面減少 TextView 中一些不必要功能的耗時,另一方面方便實現自定義截斷等特殊功能,整體架構層級與 iOS 差別不大,此處不再贅述。
三、優化效果
藉助通用富文字能力+非同步繪製框架,iPhone6 上的複雜富文字場景下,平均幀率可保持在 55+,提升明顯。
離屏資源管控框架
當前動圖、影片均作為高頻使用能力,在資訊流場景中有較大佔比,而同時,這類能力對裝置 CPU 佔用也是極為明顯的,若不加以管控,較容易能夠復現發熱、降頻、卡頓現象。在當前 DX 的服務範圍涵蓋集團內大部分 App 的背景下,瞭解到影片、動圖等能力均為較高頻使用能力,所以 DX 團隊在框架層面抽象了一套通用播控框架,致力於解決當前這種多影片、多動圖等場景下的播控能力缺失及 CPU 資源浪費問題。
一、設計原理
離屏資源管控框架整體採用分層設計:
-
底層核心曝光邏輯層主要實現核心訊息轉發流程,對業務原有邏輯無侵入性,基於該實現,監聽 cell 生命週期進行自動曝光,針對曝光的 cell 還會進行時間校驗和麵積校驗,在符合時間和麵積條件後將節點資訊傳遞至上層框架消費該訊息。核心曝光邏輯層可單獨作為通用曝光能力使用,並不依賴上層播控邏輯;
-
中間播控邏輯適配層主要重寫代理類以支援與曝光不同的生命週期;
-
播控邏輯層實現了播控框架本身的主要能力:
-
作為底層曝光邏輯層的代理,處理曝光邏輯層上報的 index 等資訊,根據對應資訊尋找上層對應訊息介面的實現物件,建立訊息通道連線;
-
根據查詢到的對應訊息代理處理播控佇列的建立和更新;
-
播放和停止訊息傳遞,播放完畢回撥接收,控制播放佇列進行下一元素播放;
-
適配多容器場景,容器之間互相隔離,相同容器的不同場景之間互相隔離;
方案整體利用 OC 的 NSProxy 直接進行訊息轉發,採用 AOP 的形式,對原有邏輯流程無侵入性;對於同一個容器物件可註冊不同場景,每個場景相互隔離,互不影響。
二、管控流程
播控框架提供兩種播控方式供業務方自行選擇,自動播控/手動播控。
當使用自動播控時,會監聽卡片上屏/離屏等時機
-
當接收到上屏訊息時:
-
底層曝光邏輯會對卡片的面積和停留時間進行校驗,對於停留時間和麵積比符合條件的位置資訊會進行記錄並上報上層播控邏輯層;
-
上層播控邏輯層會根據該 index 尋找到當前卡片和其中符合條件的元件,可能為 1 個或多個,使用者可根據配置調整順序;
-
找到符合條件的播控節點後會判斷當前可播控的個數與正在播放的數量比,如果符合則直接觸發播放,若不符合將其放入佇列後,等待播放;
-
當接收到離屏訊息時:
-
底層曝光邏輯層會找到當前容器中符合條件的位置資訊並進行上報;
-
上層播控邏輯層會直接找到當前播控邏輯層記錄的正在播放的物件和待播放的佇列,將部分符合該位置資訊的物件移除佇列,其餘物件仍舊正常進行播控。
手動播控會在使用者觸發時遍歷當前屏上可見 cell,將其 index 主動進行上報。其餘流程與自動流程一致。
三、優化效果
通過效能測試分析,影片資源比較多的時候對整體 CPU 影響極大,以 iPhoneX ,手淘首頁資訊流為例,如果不加播控能力在一屏有多影片場景下慢速滑動都可以容易復現出降頻的場景。
加入資源管控後對影片 mediaServerd 程序在裝置內整體佔比有較大正向影響,影響也是跟播控時間停留筏值呈正相關。在影片較多的場景下,在加入播控後的 mediaServerd 在裝置中的整體 CPU 佔比可降為原來的 1/4 ~ 1/3。整體 CPU 資源優化 65% 以上。
優化效果總結
DX 的效能優化與業務方密不可分,藉助於多個複雜業務方的效能問題分析,驅動 DX 的效能進一步優化,而 DX 的效能優化能力,也在第一時間輸出業務方,為業務方解決問題。以訂閱首頁資訊流為例,在 DX 的多種優化及業務方自己的部分優化結合後,在 iPhone6 低端機上,幀率從原來 30 幀左右提升到了平均 50 幀以上,效能提升不僅表現在資料上,使用者體感也同樣明顯:
總結和展望
通過上述對 DX 最近一年來的效能優化總結,我們為 DX 解決了不少原有效能問題:圓角、漸變色、文字、圖片元件等,也增加很多新的優化方式和可能性:管線非同步化、非同步繪製及富文字、離屏資源管控框架等。幫助手淘內外多個業務方排查和解決了不少效能問題,在多個複雜業務上真正幫助業務方實現了低端機平均幀率 50+ 的目標。但作為高效能動態化框架的代表,我們做的還遠遠不夠,例如:
-
當前 DX 雖然是單向資料驅動,但對於富互動的動態卡片支援並不友好,資料流並未形成環,僅比較適合靜態展示卡片,一旦涉及複雜互動,開發者就必須在客戶端自定義處理事件。理想情況下,view 應該是狀態的函式輸出,而 view 也可以通過發出 action 來影響狀態,而進一步自動改變 view 樣式,在這種情況下,讓單向資料流形成環,響應式就顯得十分自然;
-
當前 DX 採用自定義 XML 並且使用邏輯與 UI 分離的形式描述 UI,這一方面是 DX 高效能的基礎,避免了邏輯影響渲染流程。而另一方面,使用自定義非標準的 XML 描述 UI,形式過於陳舊,也阻礙了 DX 的通用性;邏輯與檢視分離的描述形式,對模板開發者限制較多,也提升了模板開發者的成本。在響應式框架較為流行的當下,是否有一種方案可以讓開發者比較自然的使用現代宣告式 DSL 來描述檢視和邏輯,並且能夠持續保持 DX 的高效能水準;
-
當前 DX 的渲染完全依託於平臺 NativeUI,基本無自繪能力。一方面,保障了使用系統元件時可以利用系統本身的優化特性及穩定性,例如列表容器的滾動優化,iOS 平臺本身已經針對此做了大量優化和擴充套件點。但另一方面,完全依託於平臺特性進行渲染也失去了自繪的諸多可能性,也難以完全保證雙端一致性,跨平臺的最終形態是否為自繪還未可知,後續是否需要在自繪和 Native 渲染結合進行深一步探索,支援在自繪渲染和 NativeUI 之間靈活切換以滿足業務方的多種需求和跨平臺支援。
效能優化方向沒有止境,後續我們還會繼續帶著上述問題進行深一步探索和行動。
關注我們,阿里前沿移動乾貨&實踐給你思考!
- SLS:基於 OTel 的移動端全鏈路 Trace 建設思考和實踐
- 文字佈局效能提升 60%,Inline Text 技術原理與實現 | Cube 技術解讀
- Android Target 31 升級全攻略 —— 記阿里首個超級 App 的坎坷升級之路
- 系統困境與軟體複雜度,為什麼我們的系統會如此複雜
- 系統困境與軟體複雜度,為什麼我們的系統會如此複雜
- Cube 技術解讀 | Cube 渲染設計的前世今生
- 淘寶Native研發模式的演進與思考 | DX研發模式
- 大量模組殼工程本地如何快速編譯?優酷 iOS 工程外掛化實踐
- 大量模組殼工程本地如何快速編譯?優酷 iOS 工程外掛化實踐
- 從0到1,IDE如何提升端側研發效率?| DX研發模式
- 優酷移動端彈幕穿人架構設計與工程實戰總結
- 2022 支付寶五福 |“聯機版”打年獸背後的網路技術 RTMS
- Flutter 圖片庫重磅開源!
- 如何持續突破效能表現?DX 效能優化策略詳解
- 淘寶Native研發模式的演進與思考 | DX研發模式
- 前車之鑑:聊聊釘釘 Flutter 落地桌面端踩過的“坑” | Dutter
- 釘釘 Flutter 跨四端方案設計與技術實踐 | Dutter
- 前車之鑑:聊聊釘釘 Flutter 落地桌面端踩過的“坑” | Dutter
- Dutter | 前車之鑑:聊聊釘釘 Flutter 落地桌面端踩過的“坑”
- Dutter | 釘釘 Flutter 跨四端方案設計與技術實踐