IM跨平臺技術學習(九):全面解密新QQ桌面版的Electron內存優(yōu)化實踐

本文由QQ技術團隊分享,本文收錄時有內容修訂和大量排版優(yōu)化。
1、引言
QQ 作為國民級應用,從互聯(lián)網(wǎng)興起就一直陪伴著大家,是很多用戶剛接觸互聯(lián)網(wǎng)就開始使用的應用。
而 QQ 桌面版最近一次技術架構升級還是在移動互聯(lián)網(wǎng)興起之前,在多年迭代過程中,QQ 桌面版也積累了不少技術債務,隨著業(yè)務的發(fā)展和技術的進步,當前的架構已經(jīng)無法很好支撐對 QQ 的發(fā)展了。
在 2022 年初,我們下定決心對 QQ 進行全面的技術架構升級,對于這樣一個國民級應用的重構,挑戰(zhàn)無疑是巨大的。
新版桌面 QQ 自內測以來也受到許多熱心網(wǎng)友和行業(yè)人士的關注,非常感謝大家在內測過程中提的各種有建設性的建議和反饋。其中,也有一小部分有開發(fā)背景的用戶對我們采用 Electron 框架表達擔心:高內存占用、超大安裝包、啟動緩慢等。究其原因還是擔心新版本 QQ 資源占用大、體驗變差,針對用戶的擔心,我們在內存上進行了專項優(yōu)化,也取得了一些階段性的進展,過程中也積累了不少經(jīng)驗,也借此機會分享給大家。
本文我們將和大家分享新版 QQ 在內存優(yōu)化方面的探索和階段性優(yōu)化進展。雖然本文的討論主要集中在 Windows 平臺,但由于 Electron 的跨平臺特性,大部分優(yōu)化措施也同樣適用于 macOS 和 Linux 平臺。

?
技術交流:
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發(fā)布于:http://www.52im.net/thread-4429-1-1.html)
2、系列文章
本文是系列文章中的第9篇,本系列總目錄如下:
《IM跨平臺技術學習(一):快速了解新一代跨平臺桌面技術——Electron》
《IM跨平臺技術學習(二):Electron初體驗(快速開始、跨進程通信、打包、踩坑等)》
《IM跨平臺技術學習(三):vivo的Electron技術棧選型、全方位實踐總結》
《IM跨平臺技術學習(四):蘑菇街基于Electron開發(fā)IM客戶端的技術實踐》
《IM跨平臺技術學習(五):融云基于Electron的IM跨平臺SDK改造實踐總結》
《IM跨平臺技術學習(六):網(wǎng)易云信基于Electron的IM消息全文檢索技術實踐》
《IM跨平臺技術學習(七):得物基于Electron開發(fā)客服IM桌面端的技術實踐》
《IM跨平臺技術學習(八):新QQ桌面版為何選擇Electron作為跨端框架》
《IM跨平臺技術學習(九):全面解密新QQ桌面版的Electron內存占用優(yōu)化》(* 本文)
3、新版 QQ 在內存上的挑戰(zhàn)
新版 QQ 在內存上的挑戰(zhàn)主要表現(xiàn)在以下 4 個方面:
1)產(chǎn)品形態(tài):
由 1 個復雜的大面板(100+ 復雜程度不等的模塊)和一系列獨立功能窗口構成。窗口與渲染進程一一對應,窗口進程數(shù)很大程度影響 Electron 的內存占用。
對于那個復雜的大面板, 一旦沒有精細控制就很容易導致內存持續(xù)走高。

▲ Electron 窗口多進程示意
2)使用習慣:
用戶長時間掛機。相比用完即走的 Web 頁面,QQ 用戶在一次登錄后,可能會掛機一個月以上。這段期間,如果沒有控制好 QQ 內存使用,那么結果可能是內存越占越大、用戶交互響應變慢、甚至發(fā)生閃退。
3)版本迭代:
已經(jīng) 24 歲的 QQ 擁有眾多的功能和特性。
過去一年我們一直做這件事——從核心特性開始快速補齊 Windows 版本的功能,同時也有一些高優(yōu)先級的新功能要上。持續(xù)且快速的版本迭代,很可能產(chǎn)生新問題,使性能劣化。
4)應用架構:
新版 QQ 依賴一個 NT 核心數(shù)據(jù)模塊(C++ addon),為 UI 提供本地化的數(shù)據(jù)服務。QQ 的加載體驗能做到如此絲滑,這個模塊起到了至關重要的作用。
同時,與 NT 的聯(lián)動優(yōu)化,也需要拉通客戶端 C++ 開發(fā)同學共同完成。當然,會存在一些溝通成本,但不可否認,能把內存占用壓下來,客戶端同學也付出了非常多的努力。

▲ 新桌面端 QQ 整體架構
4、新版 QQ 的內存現(xiàn)狀與優(yōu)化目標
在著手優(yōu)化之前,我們結合舊版 QQ 以及其他優(yōu)秀的桌面應用,給新版 QQ 設定了優(yōu)化目標:
1)第一階段目標:單個進程內存 < 300M。
2)第二階段目標:單進程 <100M,整體 < 300M。
針對第1)階段目標:早先因為沒有騰出手處理內存問題,代碼中存在一些泄漏。長時間掛機后比較容易出現(xiàn)單個進程超過 300M 的情況。我們在去年 9 月份系統(tǒng)地處理過一波內存問題,基本可以保證單個進程的內存占用 < 300M。
針對第2)階段目標:整體是指啟動 QQ 聊天面板后,6 個進程內存占用之和。內存達標之后才允許交付新版 QQ Windows 版本。

▲ Windows 任務管理器的 QQ 內存占用詳情
這些進程會隨著 QQ 的啟動一直存在。我們重點看下這 3 類進程,這也是內存優(yōu)化的大頭:
1)node:Electron 的主進程,負責窗口管理、跨進程通信等。包含 NT 核心數(shù)據(jù)模塊,負責與服務端交互,為 UI 提供數(shù)據(jù)服務;
2)renderer:Chromium 內核的渲染進程,負責渲染 UI、提供用戶交互等。QQ 啟動后,會有 2 個渲染進程:一個是 QQ 大面板,另一個是主進程的窗口池。窗口池是預創(chuàng)建的一個渲染進程。在新開窗口時,可以減少等待時間;
3)gpu:Chromium 內核的 GPU 進程。它的主要作用是處理與圖形相關的任務,例如渲染網(wǎng)頁、播放視頻、執(zhí)行動畫等。
設定了目標后:我們先對 QQ 的內存占用情況進行了摸底。我們從用戶的角度出發(fā),使用 Windows 任務管理器來觀察 QQ 的內存占用情況。我們先從最簡單的 “Hello World” 開始,看看 Electron 應用的最低內存需求是多少,以及上限在哪里。結果顯示,只需要 68M,并沒有達到傳說中的幾百 M 那么大。
然而:隨著使用的深入,比如在 QQ 聊天場景中進行一些操作之后,主進程、GPU 進程和渲染進程三個進程的內存占用就已經(jīng)達到了 600M。這意味著我們距離目標還有超過 50% 的優(yōu)化空間。

(注:AIO 是聊天面板的簡稱)
這個初步的觀察讓我們看到了目前的挑戰(zhàn),同時也讓我們看到了優(yōu)化的可能性。我們有信心,通過精心設計和持續(xù)優(yōu)化,逐步接近甚至超越我們設定的目標。
5、內存優(yōu)化我們都做了什么
接下來,將重點介紹我們是如何掌控和優(yōu)化 Electron 的內存的。
我們的工作主要包括以下幾個方面。
1)工具分析:首先,我們需要使用不同維度的內存分析工具,從 V8 引擎到進程,再到整個應用程序,打通整個鏈路進行多角度的細節(jié)分析,以此來定位內存使用的瓶頸。
2)定向優(yōu)化:在通過工具定位到問題之后,我們會采取一系列的針對性優(yōu)化策略,包括緩存策略、按需加載、優(yōu)雅降級等。具體的優(yōu)化工作我們將在后面進行詳細介紹。
3)線上監(jiān)控:在本地或小范圍內驗證通過之后,我們需要廣大用戶的驗證來確認我們的優(yōu)化措施是否適用于所有場景。然而,如何獲取用戶在 Windows 任務管理器中看到的內存使用量是一個挑戰(zhàn),我們已經(jīng)做了大量的研究和驗證。
4)防止性能退化和自動化測試:為了保護我們辛苦得來的優(yōu)化成果,并避免頻繁的版本迭代影響 QQ 的內存目標,我們會借助開發(fā)框架、工具建設、代碼審查等手段來預防性能退化。
6、選擇合適的分析工具
在進行性能優(yōu)化之前,我們需要選擇合適的工具來幫助我們分析問題。
QQ 的代碼不僅包含 V8 的 JS 部分,還包括許多 Native 的 C++ 模塊。僅依靠 Chromium 開發(fā)者工具進行性能分析是不夠的,因此我們需要組合使用多種工具來共同解決問題。

這些工具如何使用,由于篇幅的關系我們在這里不做詳細介紹。

▲ 部分內存分析工具截圖
7、定向優(yōu)化1:最大化資源使用率
7.1代碼及靜態(tài)資源
桌面版 QQ 的功能邏輯非常復雜,代碼量龐大。雖然代碼不需要通過網(wǎng)絡請求加載,本地加載速度通常較快,但加載如此龐大的代碼會占用大量內存。
因此,仍然需要進行代碼瘦身、靜態(tài)資源優(yōu)化、分包和按需加載等優(yōu)化措施。

▲ Devtools > Memory 分析 QQ 主窗口內存占用
首先是代碼瘦身:對于第三方包或 SDK,它們往往包含了完備的 Web 兼容性及能力,而這些對于 Electron 客戶端來說并不是必需的。因此,我們會對它們進行定制裁剪或獨立實現(xiàn),以減少代碼的加載。
對于 QQ 的業(yè)務代碼:分包策略不完全按照每個頁面(窗口)以及模塊復用次數(shù)來進行制訂,更多的情況是按照場景模塊來進行細粒度的定制。
以打開一個窗口到進入使用場景為例:
1)窗口池中預啟動的窗口頁面只加載必須執(zhí)行的基礎代碼;
2)當打開具體窗口時加載對應的路由后頁面入口代碼;
3)當具體使用不同功能時動態(tài)加載(如點擊搜索、打開表情面板、轉發(fā)消息激活好友選擇器的時候才會分別加載對應功能模塊代碼)。

?
▲ QQ 主窗口業(yè)務模塊的拆解
此外:其他靜態(tài)資源(如 SVG、base64 圖像)在加載時也會占用不少內存,所以我們采取了按需加載的策略(只在可見時加載,不可見時主動銷毀和回收)。

▲ svg 及 base64 資源的 string 內存占用
為了提升執(zhí)行效率和代碼保護的目的,我們將 JS 代碼轉成了字節(jié)碼。盡管跳過了源碼編譯,直接將字節(jié)碼交給 V8 執(zhí)行,但在程序報錯還原堆棧等運行時步驟中,V8 仍然會引用源碼字符串。為了去掉這份源碼,我們使用和源碼等長的空格來占位,但通過 devtool 檢查發(fā)現(xiàn)這些空格字符串仍會占用不少內存空間。最終,我們采取修改和移除 V8 對源碼字符串引用的方式,徹底解決了源碼字符串的內存占用問題。

7.2圖片資源
QQ 作為一款 IM 工具,會涉及到大量的圖片收發(fā)。然而,圖片的渲染會占用相當大的內存。
舉個例子:一張分辨率為 4000 x 2750 的圖片,結合設備屏幕像素和聊天區(qū)設計尺寸,只需渲染寬度為 567 像素的分辨率圖像即可清晰展示。如果以寬度為 4000 像素的分辨率渲染,理論上兩者位圖所占用的內存大小差距可達 50 倍,并且還會因為渲染帶來性能損失。

▲ 圖片尺寸對內存影響舉例
在聊天消息列表中的大部分圖片僅僅起到預覽作用,縮略圖渲染就滿足了需要。而僅僅在用戶真正打開圖片查看器放大查看時,才會需要用原圖渲染。
實測在聊天中多張不同大尺寸分辨率圖片在展示時,渲染進程和 GPU 進程的內存占用有著明顯差別。在收發(fā)圖片時,我們會根據(jù)屏幕設備信息和計算展示區(qū)域所需實際渲染分辨率,當原圖分辨率超出計算所需值,則先調用壓縮服務進行圖片壓縮,生成渲染所需分辨率的縮略圖,并在聊天區(qū)域進行渲染上屏。在這個策略的優(yōu)化下,一般聊天圖片場景測試下來,使用縮略圖比原圖約有 30M ~ 50M 的內存優(yōu)化。

▲ QQ 優(yōu)化圖片上屏策略
8、定向優(yōu)化2:可視區(qū)域按需渲染
8.1DOM 元素數(shù)量
在 DOM 元素使用數(shù)量我們也有嚴格的控制,總體采用”所見即占用“的 DOM 渲染策略。在 QQ 大面板中只有視口所見的內容才會渲染對應 DOM 元素。其他所有組件在不渲染展示時,均會移除組件及其 DOM 元素來避免其內存開銷。

▲ 大虛擬列表控制 DOM 數(shù)量
尤其對于各個大列表模塊:比如聯(lián)系人列表和群成員列表,DOM 元素都非常多。最開始的內測版本中,使用有大量好友和群聊的 QQ 號,窗口平均 DOM 數(shù)達到 13000。我們將 QQ 所有的普通分頁列表替換為虛擬滾動列表,并且對列表滾動 buffer 進行極限壓縮甚至是 0 buffer 。由于不再一味采取空間換時間,沒有 buffer 的情況下必然面對列表滑動性能挑戰(zhàn),因此也需不斷優(yōu)化各類 item 組件渲染性能。
此外:我們還通過精簡組件 DOM 層級,移除非核心組件 keep-alive(重新優(yōu)化渲染性能)等方式,大賬號使用下整體的 DOM 數(shù)量從 13000 減少到控制在平均 4000 以內,這部分優(yōu)化減少約 20M 內存。
8.2渲染圖層
渲染圖層方面,在渲染時滿足某些特殊條件的渲染層,會被瀏覽器自動提升為合成層,達到提升渲染性能的目的。但是每個合成層都占用額外的內存,應當去掉過量且不必要的合成層來控制圖層帶來的內存占用。當然結合渲染性能考量,對于高頻且列表等核心模塊,是可以單獨提升合成層。

▲ QQ 對于渲染合成層的優(yōu)化處理
在桌面端 QQ 中通過超級調色盤可以為進行色彩換膚,在這個場景中全局各模塊有不少單獨提升的合成層來實現(xiàn)毛玻璃、漸變和紋理效果。另外還有許多不經(jīng)意間被提升的隱式合成層。通過對不必要的合成層進行移除與合并,整體也優(yōu)化了約 9.3M 內存。
8.3結構化消息
QQ 支持豐富的消息類型,從簡單的文本、圖文消息,到復雜的 lottie 表情、下圖所示的業(yè)務可定制的結構化消息等。我們知道 JavaScript 是單線程的,這些消息同時上屏的時候可能會出現(xiàn)過長的上屏任務而導致 UI 卡頓,給到用戶的感受就是切換消息列表卡頓,消息上屏慢等糟糕的體驗。
新版 QQ 針對這類復雜消息上屏,使用了 JavaScript 事件機制結合 WebWorker 來實現(xiàn)消息異步上屏,并使用 OffscreenCanvas +??Worker 池繪制來提升渲染性能。

▲ QQ 結構化消息的處理方案
為了在 Canvas 中實現(xiàn) CSS 的 Flex 布局效果,我們采用了跨平臺的布局解決方案,將 Yoga 編譯成 WebAssembly 運行在 WebWorker 中。Yoga 官方編譯采用的是?asm.js?的方案, 這種方案不支持動態(tài)分配內存,可以看到它默認分配了一個較高的內存,達到了 128M。

▲ Yoga 渲染引擎的原始內存占用
為了優(yōu)化 WebAssembly 的內存占用,我們調整了編譯方式,將 Yoga 編譯成獨立的 wasm 文件,這種方式相比?asm.js?支持動態(tài)內存分配。
同時結合聊天窗口的消息卸載策略,經(jīng)過不斷的測試調優(yōu),在既要保證初始內存較少又要盡可能避免內存爆發(fā)式增長帶來的性能損耗的前提下,我們把 WebAssembly 的初始內存分配優(yōu)化到 2M,再加上對象共享、享元模式等策略,WebWorker 的內存占用有了非常可觀的優(yōu)化。

▲ QQ 結構化消息渲染引擎優(yōu)化前后的內存占用對比
復雜的聊天消息雖然是必不可少的功能,但是實際的消息量還是遠少于普通的圖文消息,因此在保證用戶體驗的前提下,在合適的條件下適時銷毀 WebWorker 是一個合理的策略,而隨著 WebWorker 被銷毀這個線程所占用的內存也能被完全釋放。
9、定向優(yōu)化3:性能與體驗的平衡
9.1Lottie 及動畫方案選型
超級表情采用 Lottie 動畫技術方案,有高清高幀率高質量特點,但同時也為我們帶來了渲染的高成本。
為了保證 Lottie 的高幀率和減少 CPU 占用,我們緩存了 Lottie 渲染器生成的動畫幀,內存消耗成為了首要問題。
▲ QQ Lottie 動畫示例
對其進行定量分析:超級表情 Lottie 資源繼承自手機 QQ,尺寸是 512 × 512,動畫幀以 int8 數(shù)組存儲,所以一幀動畫為 512 × 512 × 4 / 1024 bit= 1024 Kb = 1Mb。一個普通大小的超級表情,例如慶祝表情,有 160 幀動畫,依據(jù)緩存 9/10 幀動畫的策略,慶祝表情會占用 144Mb 內存。雖然是可回收的,但也無疑是巨大的內存消耗。
關注到 Lottie 渲染的內存消耗后,我們主要從以下 2 步入手:
1)緩存的動畫幀尺寸:桌面端 lottie 渲染大小為 120 × 120,考慮到需要保持 Lottie 動畫的高質量,緩存的動畫幀尺寸調整為實際尺寸大小的兩倍,即 240 × 240,降低內存消耗 72%。經(jīng)設計確認,清淅度上也沒有明顯的差異;
2)緩存策略:緩存 9/10 的動畫幀減少到緩存 3/4,降低內存消耗 35%,而且調整之后幀率還能得到保障。
通過以上 2 步,一共降低內存消耗 81.8%,慶祝表情從 144 Mb 降低到 35 Mb。
最后:舊策略對于渲染過且暫時不用的 Lottie 表情,會 buffer 它的第一幀,總共 31 個 Lottie 表情(2.3k * 31 = 7M(最多)),經(jīng)評估之后,我們暫時也拿掉了該策略。
▲ QQ Lottie 動畫緩存首幀對內存的影響
另外:桌面 QQ 左側導航欄目,為了與移動端統(tǒng)一體驗,使用 Lottie 動畫來實現(xiàn),從 memory 面板來看, 4 個 icon 導航條會占用約 6M 的內存。改用 CSS 實現(xiàn),不僅效果與 Lottie 的幾乎一致,而且這 6M 的內存占用就完全省掉了。

▲ QQ 導航條動畫對內存的影響
9.2APNG 動畫優(yōu)化
APNG 是一個基于 PNG 的位圖動畫格式,后綴名也是.png,在一些類似背景圖的場景下會使用。
在早期的超級調色盤中,為了實現(xiàn)最佳炫彩效果,選用了由 300 張 15KB 靜態(tài)圖合成的配色漸變的 apng 圖片,其大小達到了 4.2M。
不過帶來的問題是渲染的延遲感。經(jīng)過和設計討論,在不影響效果體驗的基礎上,進行了大量的壓縮,壓縮到了 157KB(壓縮率超過 96%)。

9.3聊天列表與消息
聊天列表 AIO,作為 QQ IM 模塊中最主要的承載消息數(shù)據(jù)展示模塊,其滾動體驗必然離不開用戶體驗與內存的權衡。
聊天列表在靜態(tài)與滾動過程中,維持消息組件的數(shù)量多少決很大程度決定整個 QQ 的內存占用。消息數(shù)據(jù)從服務端拉取后會存儲在本地 DB,根據(jù)策略會將當前會話的消息數(shù)據(jù)緩存在內存中。
隨著滾動加載,消息緩存占用的內存也越多。所以也有一定動態(tài)閾值的策略,丟棄滾動方向相反的舊消息,從而將內存控制在可接受范圍。如果用戶重新操作又需要加載時,這請求底層向本地磁盤 DB 重新拉取。
▲ QQ 聊天消息列表的加載策略
消息組件實例是內存占用的大戶,每條消息組件內部包含頭像 / 昵稱 / 狀態(tài) / 內容等多個實例,如果不對消息實例進行回收銷毀,每百條消息約能帶來 20M+ 的內存增量,因此消息實例的回收策略尤為關鍵。
最早版本中對消息上屏沒有丟棄策略,內存增量沒有很好控制。于是采用分頁列表,屏內保持固定幾頁消息(約 30 ~ 50 條消息,視屏幕尺寸決定),超過范圍的消息進行丟棄,列表高度由屏內消息直接撐起,用戶通過觸頂或觸底進行上下一頁消息的加載。
但這頁帶來些點問題:一方面隨著觸頂觸底,滾動條頻繁跳動的體驗并不好。另一方面列表高度由不定高的組件渲染消息來維持,不得不始終保留 30 ~ 50 條消息以撐起滾動高度,不可見消息的那部分便造成內存的浪費。
使用虛擬列表維持計算高度后,列表不再依賴保持真實消息內容的渲染,理論上我們可以將可視區(qū)域以外的消息實例全部銷毀,僅保留用戶可見的消息,最大程度地壓縮消息實例數(shù)量,指保留很少的 buffer 消息實例。在實際滾動中由于消息實例在滾動過程被不斷創(chuàng)建和銷毀,占用主線程,影響 UI 繪制和用戶輸入。
因此我們還做了:
1)對創(chuàng)建銷毀做一定聚合,批量處理消息上屏;
2)精簡優(yōu)化單條組件的渲染性能;
3)不同滾動方向調整上下不同 buffer 大小 等等措施;
4)會話切換和窗口聚失焦最小化等操作時對不再使用的消息資源內存進行主動回收。
▲ QQ 聊天消息列表的上屏策略
滾動性能和內存占用之間需要取得平衡,既要最大程度壓縮上屏消息數(shù)量以節(jié)省內存,又要保證滾動性能體驗。然而經(jīng)過優(yōu)化后,本地測試加載 200 條混合種類的消息場景下,從空狀態(tài)進入聊天會話中,消息列表內存增量從最多 44.2M 降至 6.1M,且滾動靜止后內存不會任意增長。
10、定向優(yōu)化4:Electron的正確使用姿勢
Electron 給主進程提供了不少對系統(tǒng)能力調用的 API,如托盤、系統(tǒng)通知、macOS 中 dock 欄設置等。但是如果對這些 Electron 能力的使用方式不對,就可能導致不必要的大量內存占用甚至是泄漏。
比如 QQ 中:我們通過短間隔定時調用 Tray setImage API 來實現(xiàn) QQ 托盤的閃爍,如果不注意傳入 string Path 則會每次創(chuàng)建 Image 對象導致內存占用,正確的方式應該創(chuàng)建 NativeImage 并緩存,調用 Tray setImage 傳入指定 NativeImage,避免反復創(chuàng)建 Image 導致的內存問題。
?
▲ Windows 托盤圖標內存泄漏定位
類似的問題還有在 Mac OS 中調用 API dock.setIcon 也會持續(xù)占用約 20M 的 CGImage 位圖內存,正確的方案應該是不通過 Electron API 指定,而是通過打包 plist(屬性文件) 指定 dock 欄圖標。
▲ Mac OS dock 圖標內存泄漏定位
在使用 Electron 的過程中,還存在類似會導致內存問題的使用方式,我們需要結合客戶端內存工具進行深度挖掘和分析,才能發(fā)現(xiàn)和處理這些問題。
11、定向優(yōu)化5:消滅內存泄漏
我們知道 V8 有自己的垃圾回收機制,雖然它在 GC(垃圾回收)方面有著其各種策略,并做了各種優(yōu)化從而盡可能的確保垃圾得以回收,但我們仍應當避免任何可能導致無法回收的代碼操作。
常見的例子包括:
1)未移除的監(jiān)聽器和定時器:在監(jiān)聽事件處理函數(shù)其中引用的不被釋放導致的泄漏;
2)游離 DOM 未釋放:移出 document 后游離 DOM 仍存在引用導致無法釋放。較多發(fā)生于框架的組件銷毀時,相關監(jiān)聽未取消導致組件沒有釋放的情況;
3)監(jiān)控 / 打點導致的泄漏:在使用 Performance.mark 打點監(jiān)控時,產(chǎn)生 PerformanceMark 對象,在用完之后沒有手動清除,也會導致內存泄漏;
4)console.error 導致的泄漏:控制臺持有被打印對象始終不釋放,導致應用的泄漏;
5)其他不當?shù)拈]包及隱式的全局變量。
以上是桌面 QQ 在早期遇到的常見問題。后續(xù),我們通過代碼檢測手段來防范這類問題的出現(xiàn)。
與一般的前端項目不同,由于桌面 QQ 的長周期使用特性,任何緩慢而微小的內存泄漏都可能被放大,這也是我們極力把控并阻止任何可能導致內存泄漏的代碼引入的原因。
12、優(yōu)化結果與線上監(jiān)控
經(jīng)過一系列組合優(yōu)化之后,在我們自己的設備上來看,QQ 的內存使用基本是達標了,長時間掛機穩(wěn)定在 300M 以下,但在廣大 QQ 用戶側能否保持這個水平?只有通過線上內存及性能的采集監(jiān)控,才有數(shù)據(jù)指標來觀測,從而才能對優(yōu)化有效性進行驗證和決定如何調整優(yōu)化方向。
好在 Electron 提供了 app.getMetics 、 process.getMemoryInfo 等 API 來采集內存指標。
但需要注意的是:這些 API 所采集返回的內存值的真實含義,如 getMetric 所采集的到 workingsetSize 和 privateBytes 均不是任務管理器用戶所看到的內存。
這里我們通過 patch 定制改造 Electron getMetics API,來增加不同平臺任務管理器的內存類型的指標,并且采集包含了主進程、渲染進程、GPU 進程和工具進程等所有內存指標。
為了避免頻繁采集上報內存指標所帶來的的性能消耗,我們設定了一定時間的采集間隔,同時針對使用場景的采集做了抽樣。并將渲染進程 pid 映射尋找窗口名,只在若干次采集后再做聚合計算,通過 SDK 上報到?prometheus + grafana?的指標觀測平臺。
▲ QQ 內存監(jiān)控整體方案
經(jīng)過若干次內存性能優(yōu)化的迭代,目前從線上數(shù)據(jù)指標來看,新版 Windows QQ 運行的內存在主場景下基本控制在 300M,這個值已經(jīng)基本達到我們設定的目標。
從登錄后使用過程中的內存指標如下:
1)整體應用的內存平均占用約為 228M;
2)中位數(shù)占用約為 211M;
3)90% 分位用戶內存占用約為 350M。
當然,這個目標只是階段性的,我們還會持續(xù)針對更多使用場景進行內存優(yōu)化。
13、防劣化與自動化測試
為了持續(xù)關注和保證新版 QQ 項目的性能達標且不劣化,除了比較常規(guī)的單元測試、代碼檢查、代碼評審機制、框架內置一些開發(fā)規(guī)范等手段外,我們還在建設一個防劣化平臺,主要通過自動化的端對端 (e2e) 測試來持續(xù)監(jiān)控項目集成后的性能變化。
主要是:
1)定時對主干上集成構建的程序進自動化 e2e 測試;
2)除了對功能的冒煙測試外,針對重點關注的性能指標,構造了對應的帳號和環(huán)境,編輯特定的用例,用于采集性能指標;
3)通過將采集和采樣的指標上報到防劣化的監(jiān)控平臺,來監(jiān)控項目集成后的性能變化,如會話切換響應時間、內存占用、CPU 使用率等;
4)監(jiān)控平臺提供按版本和時間的指標曲線、對比,方便查看和分析性能變化情況。同時打通企業(yè)微信機器人,對性能指標情況進行實時推送告警。
根據(jù)告警信息對應的版本信息和代碼記錄,排查情況,閉環(huán)問題。
▲ 防劣化機制示意圖
這一套機制之前在 內測中的 QQ 頻道桌面端的項目中嘗試應用,運行發(fā)現(xiàn)了一些比較典型的代碼異常、crash、oom 問題,證明確實有效。新版 QQ 業(yè)務和設計都更復雜,建設好防劣化機制無論是對發(fā)現(xiàn)問題的效率,還是對整體的性能和質量都是意義重大的,也是我們團隊當前重點建設、未來持續(xù)迭代的重要任務。

?
▲ 防劣化推送與告警實際應用圖例
14、本文小結
可能大家比較關心,為什么一定要選擇 Electron?
其實我們是經(jīng)過深思熟慮的:
首先:全新 QQ 意味著我們應該專注在功能快速迭代上,否則,以 QQ 的體量戰(zhàn)線會拉得非常長。
我們希望最后選擇的跨平臺方案應該是足夠成熟、低開發(fā)和使用成本,不需要為了使用框架本身,還需要投入額外巨大的人力成本。這個其實在 React Native、Flutter、Tauri 等跨平臺框架的使用過程中,我們都遇到過類似的問題,除了功能開發(fā),為了把框架生態(tài)、周邊、工具鏈建設好,還需要投入巨大的額外成本,Qt 也有類似的問題。
而使用 Electron,對于 Web 前端開發(fā)同學,基本上是 0 成本,現(xiàn)有的 Web 前端的大部分基建都可以直接復用,而且使用 Web 開發(fā) UI 的效率,在主流技術棧里算是很高的了。
并且這幾年主流的桌面端應用基本都選擇了 Electron,如 VScode、Discord、Slack、Skype、Whatsapp、Figma 等等,新的桌面應用基本上也是首選 Electron。
另外,Electron 版本的迭代速度和社區(qū)氛圍都很在線。
其次:從結果或者解決問題的角度來看,經(jīng)過一系列優(yōu)化之后基本可以將 QQ 核心聊天場景的內存控制在 300M 以內,150M 的安裝包大小,與舊版純 Native QQ 差別較小。不單單內存占用,其他核心體驗,比如切 AIO 的流暢度上要優(yōu)于舊版 QQ。即便是在今天,QQ 也堅定一年半之前選擇了 Electron。
最后:讓我們再次聚焦在內存優(yōu)化的工作上,下圖是我們在桌面 QQ 中針對 Electron 內存優(yōu)化工作的一個概覽。

▲ 桌面 QQ 內存優(yōu)化工作概覽
內存優(yōu)化沒有銀彈,有的只是一步一個腳印深入做下去,芝麻西瓜都要撿,從量變到質變。
未來我們完全有信心,憑著已有的經(jīng)驗和對其技術的理解,守住現(xiàn)在這些成果的同時,進一步優(yōu)化 QQ 生態(tài)下的各個子業(yè)務、子模塊的內存占用問題。
因此,也希望通過我們實踐經(jīng)驗分享, 讓大家從更多辯證的視角來重新看待 Electron 或類 CEF 的技術方案。
15、相關資料
[1]?Electron官方開發(fā)者手冊
[2]?快速了解新一代跨平臺桌面技術——Electron
[3]?Electron初體驗(快速開始、跨進程通信、打包、踩坑等)
[4]?Electron 基礎入門 簡單明了,看完啥都懂了
[5]?vivo的Electron技術棧選型、全方位實踐總結
[6]?融云基于Electron的IM跨平臺SDK改造實踐總結
[7]?閑魚IM基于Flutter的移動端跨端改造實踐
[8]?網(wǎng)易云信基于Electron的IM消息全文檢索技術實踐
[9]?閑話即時通訊:騰訊的成長史本質就是一部QQ成長史
[10]?技術往事:創(chuàng)業(yè)初期的騰訊——16年前的冬天,誰動了馬化騰的代碼
[11]?技術往事:史上最全QQ圖標變遷過程,追尋IM巨人的演進歷史
[12]?QQ的成功,遠沒有你想象的那么順利和輕松
[13]?還原真實的騰訊:從最不被看好,到即時通訊巨頭的草根創(chuàng)業(yè)史
附錄:更多QQ團隊分享的技術文章
《騰訊技術分享:騰訊是如何大幅降低帶寬和網(wǎng)絡流量的(圖片壓縮篇)》
《騰訊技術分享:騰訊是如何大幅降低帶寬和網(wǎng)絡流量的(音視頻技術篇)》
《騰訊技術分享:Android版手機QQ的緩存監(jiān)控與優(yōu)化實踐》
《騰訊技術分享:Android手Q的線程死鎖監(jiān)控系統(tǒng)技術實踐》
《讓互聯(lián)網(wǎng)更快:新一代QUIC協(xié)議在騰訊的技術實踐分享》
《騰訊技術分享:社交網(wǎng)絡圖片的帶寬壓縮技術演進之路》
《QQ音樂團隊分享:Android中的圖片壓縮技術詳解(上篇)》
《QQ音樂團隊分享:Android中的圖片壓縮技術詳解(下篇)》
《騰訊團隊分享:手機QQ中的人臉識別酷炫動畫效果實現(xiàn)詳解》
《騰訊團隊分享 :一次手Q聊天界面中圖片顯示bug的追蹤過程分享》
《QQ 18年:解密8億月活的QQ后臺服務接口隔離技術》
《以手機QQ為例探討移動端IM中的“輕應用”》
《騰訊原創(chuàng)分享(一):如何大幅提升移動網(wǎng)絡下手機QQ的圖片傳輸速度和成功率》
《騰訊原創(chuàng)分享(二):如何大幅壓縮移動網(wǎng)絡下APP的流量消耗(下篇)》
《騰訊原創(chuàng)分享(三):如何大幅壓縮移動網(wǎng)絡下APP的流量消耗(上篇)》
《信鴿團隊原創(chuàng):一起走過 iOS10 上消息推送(APNS)的坑》
《騰訊信鴿技術分享:百億級實時消息推送的實戰(zhàn)經(jīng)驗》
《IPv6技術詳解:基本概念、應用現(xiàn)狀、技術實踐(上篇)》
《IPv6技術詳解:基本概念、應用現(xiàn)狀、技術實踐(下篇)》
《騰訊TEG團隊原創(chuàng):基于MySQL的分布式數(shù)據(jù)庫TDSQL十年鍛造經(jīng)驗分享》
《了解iOS消息推送一文就夠:史上最全iOS Push技術詳解》
《騰訊資深架構師干貨總結:一文讀懂大型分布式系統(tǒng)設計的方方面面》
《騰訊音視頻實驗室:使用AI黑科技實現(xiàn)超低碼率的高清實時視頻聊天》
《騰訊技術分享:微信小程序音視頻與WebRTC互通的技術思路和實踐》
《騰訊技術分享:GIF動圖技術詳解及手機QQ動態(tài)表情壓縮技術實踐》
《社交軟件紅包技術解密(一):全面解密QQ紅包技術方案——架構、技術實現(xiàn)等》
《社交軟件紅包技術解密(九):談談手Q紅包的功能邏輯、容災、運維、架構等》
《社交軟件紅包技術解密(十):手Q客戶端針對2020年春節(jié)紅包的技術實踐》
《QQ設計團隊分享:新版 QQ 8.0 語音消息改版背后的功能設計思路》
《微信技術分享:揭秘微信后臺安全特征數(shù)據(jù)倉庫的架構設計》
《IM跨平臺技術學習(九):全面解密新QQ桌面版的Electron內存占用優(yōu)化》
(本文已同步發(fā)布于:http://www.52im.net/thread-4429-1-1.html)