淺談面向客戶端的性能優化

有朋友通過《智能音箱場景下的性能優化》一文找到了我,既然智能音箱的性能優化相當於一個超集,那麼對其的一個子集——客戶端系統如何進行性能優化呢?

反正隔離在家,不妨對客戶端的性能優化梳理一下。

我思故我在

首先,回顧一下性能優化。性能優化是面向時間的藝術,簡單的說,就是在不影響系統正常運行的前提下,執行得更快,完成特定功能所需的時間也更短。關於性能爲王的更多描述,可以參考《深入分佈式緩存》一書。

客戶端系統的性能優化可能是一種不太準確的說法,所有的性能優化都是爲了更好的用戶體驗,客戶端系統的性能優化大概是指如何優化客戶端系統已獲得更好的用戶體驗。然而,客戶端不是獨立存在的,面向客戶端系統的性能優化同樣需要服務端的配合才成。

既然是提升用戶體驗,就需要抓住重點,哪些纔是性能問題的關鍵部分呢?客戶端系統在應用整體性能中處於怎樣的地位呢?客戶端優化對整體性能的影響大麼?

實際上,很多時候,性能的瓶頸確實是在客戶端,老碼農對經歷的多個系統進行過時延分佈的統計分析,客戶端對整體性能的影響接近80%,又一個二八原則出現在了面前。

也就是說, 客戶端的性能優化大概率對系統性能有着決定性的影響。

客戶端的性能分析

客戶端又有着寬泛的概念,和大前端類似,包括了App,Web前端,小程序以及hybrid App等等。客戶端的性能優化同樣需要找到關鍵指標,用數據說話,並實現可持續的優化,監控、記錄、分析、優化的這一路徑依然有效,而且必然有效。

用戶使用客戶端,在與客戶端交互之後,等待客戶端從後臺服務器獲取內容並呈現:

  1. 客戶端的本地預處理

  2. 網絡處理:包括查詢DNS,建立連接,發送請求到服務器,並等待響應

  3. 可能是用戶在客戶端上的白屏時間,當然可以用本地緩存來改善體驗

  4. 客戶端開始接受數據,解析內容

  5. 對一般的前端而言,客戶端將讀取緩存,並執行JavaScript/CSS等,這對於用戶的首屏時間

  6. 客戶端處理數據,計算佈局,並開始渲染屏幕等,這時用戶就處於可操作的狀態了。 

以Web前端爲例的話,白屏時間一般到結束,首屏時間到首屏圖片加載結束,用戶可操作時間要到DOMReady/核心JS加載完畢。

因此,客戶端的性能優化可以分爲兩大部分:面向網絡通信的優化和麪向客戶端自身的優化。

面向客戶端的網絡優化

根據一般的經驗, 網絡通信是系統的性能瓶頸,IO慢,而最慢的IO就是網絡通信。網絡性能優化主要包括:減少網絡請求的次數,減小網絡請求的流量大小和提升網絡請求速度。

減少網絡請求次數

削減請求次數是提升通信性能的必經之路,就Web前端而言,過往的一些經驗是

  • 少使用iframe,避免404和空的src

  • 緩存Ajax的結果,儘量使用Ajax GET

  • 域名拆分,減少DNS查詢次數

  • 避免跳轉,減少HTTP連接

  • 採用延遲加載和預加載

減少請求次數往往涉及到業務流程的改造,或者涉及協議的合併與重組,有時候,還會和減小網絡請求內容大小的方法相沖突。

減小網絡請求的流量大小

網絡請求的負載一般是很難減小的,相反,隨着業務的增長,協議的膨脹,網絡請求的內容大小還會增長。對於網絡請求的淨負載而言,序列化可以在一定程度上壓縮數據內容的大小,例如使用protobuf。

另一方面,由於gzip幾乎已經成爲了基礎配置,所以網絡請求中的具體數據會採用gzip壓縮,並在壓縮後傳輸,然後在客戶端上解壓處理。特別地,對圖片而言,可以使用webp等格式對圖片進行壓縮,或者使⽤CSS Sprites也是一種辦法。

既然網絡中的數據內容難以減少,就要嘗試減少內容的無效傳輸。這是緩存技術的又一典型應用, 服務端配置Expire/cache-control,配置ETags等等都基本上是規定動作了。

提升網絡請求速度

網絡帶寬一直是短缺資源,但並不是我們無法提高網絡請求速度的藉口。例如服務端可以儘早flush數據,使用CDN更是不二法門,尤其是全站CDN,有時候能解決大部分的性能問題。

根據長短連接的特點,在客戶端選擇長短連接同樣可以在一定程度上提高請求的速度。一般地,長連接適用於點對點通信,節省TCP建立的耗時,在首次建立連接後,多次交互複用;短鏈接適用於高併發通信,節省socket資源,每次交互重新建立鏈接。

對於Web前端而言,瞭解瀏覽器所支持的連接數,可以幫助我們在帶寬充足的情況下同時使用多個連接進行通信。

瀏覽器 HTTP1.0 HTTP1.1
Chrome 6 6
Firefox 6 6
IE8 6 6
Opera 4 4
Safari 4 4

表中的數據大約是4年前的了,僅起到參考作用,技術演進很快,目前的瀏覽器對連接數有着自己規定和限制。

同時,連接池有時是性能優化的必然選擇。如果每個請求都會發起一次連接的話,會:

  • 增加延遲:域名解析、TCP握手、SSL握手、TCP慢啓動

  • 消耗流量:域名解析、握手揮手等

建立連接池可以保存空閒連接,請求優先去連接池找空閒連接,連接池命中失敗再發起新連接,可以實現Socket Late Binding,具體可以參考《從連接池到內存池》。

面向客戶端的自身優化

客戶端自身的優化,除了業務邏輯的優化之外,主要是縮短顯示佈局和渲染的時間,讓用戶感覺上“更快”。

web 優化

就web前端而言,如果使用了cookie, 需要考慮cookie大小,靜態域名最好不帶Cookie。提升CSS的性能,一般在代碼中置頂CSS,使用代替 @import ,避免CSS表達式和Filters。在SPA類型的應⽤中,要減少CSS的3D加速,減少CSS往往比減少Javascript更重要,因爲渲染的時候內存往往比CPU重要。

Javascript 的優化方式一般是前端工程師所熟知的,例如,腳本置底,JS和CSS外聯,壓縮並去掉重複腳本,減少DOM操作,使⽤事件代理等等。進一步,是數據結構和算法的優化,例如,減少查找,避免with、eval和動態改變對象屬性,使用數組來進⾏字符串拼接等等。

另一個有效的方式是減少DOM的數量,但實際上這對設計的要求很高。但是,Anyway,要減少DOM的重繪觸發,避免訪問 childNode的 數組,採用讀寫分離,收回並重複利用DOM,緩存數據⽽不是DOM本身。

App 優化

一般地,App 比web前端更能充分地利用端能力。Web頁面的生命週期較短,一般是單線程,而且持久化能力較弱。App UI 的生命週期更長,支持多線程,有工作的優先級,具備較強的持久化能力。

App 的優化一般包括預加載內容以及首圖, 最小化首屏網絡請求。根據用戶操作,提前預判處理。同樣,可以預先下載資源包,將資源靜態化上CDN。App可以通過端做連接加速(如長連接),SDK提前裝載以縮短初始化時間,並對圖片等資源進行緩存。

App同樣可以包含Hybrid 的形式, 對類web的呈現可以通過模板本地化來預渲染,可以預初始化webview,預取端能力的執行結果。爲了減少Javascript與Native 的交互,可以使用React Native來完成渲染,可以使用戶體驗得到一定的優化。

當然,App還可以對內核做針對性的優化。

客戶端的性能評估

性能優化和策略工程是類似的,沒有效果評估的性能優化都是耍流氓。

但是,客戶端的性能評估存在着一些挑戰,例如除了客戶端程序之外,還有其它程序在運⾏,JS引擎也有着JIT等動態優化,還有着各種不可⻅的緩存(如I/O、CPU cache等)。性能的評估多是基於日誌埋點,一個良好的日誌埋點系統最好能過支持按需定製的後埋點配置方式。對於客戶端系統而言,還可以有其他多種測量和評估的手段。

性能問題的定位主要是模擬出現問題的場景,如高併發,高負載情況下的系統反應。對服務端而言,一個性能測試平臺,尤其是壓測系統,一般提供兩個功能,壓力產生和數據收集。數據收集模塊負責收集數據,支持用戶自定義的數據收集邏輯。壓力產生的模塊負責多進程的管理,執行用戶的發壓邏輯(如rpc,數據庫操作),支持自定義發壓邏輯。例如,可以在裏面實現一個寫數據庫的操作,也可以實現一個讀文件的操作等等。

通過壓測,可以發現穩定性的問題,包括併發操作/訪問帶來的接口內部邏輯的異常(如多線程邏輯異常,死鎖,內存泄漏等),更主要的是發現性能問題,包括吞吐量,qps是否達到上線流量的要求等等。

對於客戶端而言,一般採用類似Monkey那樣的壓測工具,或者編寫定製的測試工具來進一步完成對性能的評估。對於類似手機上的應用軟件,一般會壓測72小時,當然,最少也要壓測4~8小時。

另外,客戶端系統的性能優化除了運行速度之外,還有對端上電量、CPU/內存等資源的考慮,從系統層面來看,都屬於性能優化的範疇。

參考資料與關聯閱讀

發佈了483 篇原創文章 · 獲贊 852 · 訪問量 154萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章