Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 506, comments - 13, trackbacks - 0, articles - 1

          本文由騰訊云開發(fā)者張曌、畢磊分享,原題“QQ 9“傻快傻快”的?!帶你看看背后的技術(shù)秘密”,本文進(jìn)行了排版和內(nèi)容優(yōu)化等。

          1、引言

          最新發(fā)布的 QQ 9 自上線以來(lái),流暢度方面收獲了眾多用戶好評(píng),不少用戶戲稱 QQ 9 “傻快傻快”的,快到“有點(diǎn)不習(xí)慣了都”。

          作為龐大量級(jí)的IM應(yīng)用,QQ 9 從哪些方面做了哪些優(yōu)化,使得用戶能夠明顯感覺到流暢度的提升?本文將詳細(xì)介紹 QQ 9 流暢背后的技術(shù)實(shí)現(xiàn),以及在全流程做的性能優(yōu)化探索,為你揭秘QQ極致絲滑背后的硬核IM技術(shù)優(yōu)化,希望在提升IM應(yīng)用流暢度這個(gè)技術(shù)方向上提供一些可借鑒的經(jīng)驗(yàn)。

           
           
           技術(shù)交流:

          - 移動(dòng)端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動(dòng)端IM

          - 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK備用地址點(diǎn)此

          (本文已同步發(fā)布于:http://www.52im.net/thread-4656-1-1.html

          2、5億人堅(jiān)持用QQ

          今年是中國(guó)開啟互聯(lián)網(wǎng)時(shí)代的第 30 年,也是 QQ 作為“初代互聯(lián)網(wǎng)產(chǎn)品”的第25年,手機(jī) QQ 的第 14 年。

          “仍有5億人堅(jiān)持用 QQ”:正是有這群用戶的堅(jiān)持,督促著 QQ 技術(shù)團(tuán)隊(duì)不斷的自我革新,為了能給用戶更好的體驗(yàn),對(duì)性能孜孜不倦的追求。

          QQ 9 開始,我們從底層架構(gòu)自底向上全部重構(gòu)優(yōu)化,解決了手機(jī)客戶端原來(lái)啟動(dòng)緩慢、容易卡、轉(zhuǎn)菊花等待時(shí)間長(zhǎng)、UI 跳變等一系列問題。上線后,收獲了用戶眾多好評(píng),其中有個(gè)高頻關(guān)鍵詞是「絲滑」,在絲滑的背后,其實(shí)是技術(shù)人吹毛求疵般的打磨。

          本文將為大家揭開 QQ 9 背后的技術(shù)探索,分享 QQ 匠人們硬核的IM優(yōu)化手段。

          3、啟動(dòng)速度優(yōu)化(極致秒開)

          QQ 的絲滑體驗(yàn)從「啟動(dòng)優(yōu)化」開始。

          以 iOS 端為例,啟動(dòng)流程主要分為 3 個(gè)階段:

          • 1)T0:點(diǎn)擊圖標(biāo)到 main 函數(shù)開始;
          • 2)T1:從 main 函數(shù)開始到 didFinishLaunchingWithOptions 結(jié)束;
          • 3)T2:didFinishLaunchingWithOptions 結(jié)束到首幀渲染完成。

          一般將啟動(dòng)過程按階段分為 pre-main (T0) 和 post-main (T1 + T2) 兩個(gè)執(zhí)行階段。

          這兩個(gè)兩個(gè)執(zhí)行階段分別是:

          • 1)pre-main 階段:系統(tǒng) dyld 加載 App 鏡像和初始化行為,與程序結(jié)構(gòu)和規(guī)模關(guān)系較大;
          • 2)post-main 階段:App 在渲染上屏前做的業(yè)務(wù)初始化行為,與具體業(yè)務(wù)邏輯關(guān)系較大。

          一般工程上的優(yōu)化方向:

          • 1)pre-main 階段降低加載和鏈接的耗時(shí):如動(dòng)態(tài)鏈接轉(zhuǎn)為靜態(tài)鏈接,代碼拆分組成動(dòng)態(tài)庫(kù)并進(jìn)行懶加載;
          • 2)post-main 階段減少主線程所執(zhí)行的代碼總量:如代碼下架,代碼執(zhí)行時(shí)機(jī)延后或異步子線程化,代碼邏輯執(zhí)行效率優(yōu)化等。

          以下就這兩個(gè)方向,介紹一下 QQ 本次做的有亮點(diǎn)的地方。

          4、啟動(dòng)速度優(yōu)化實(shí)踐1:按需裝載代碼(pre-main階段)

          動(dòng)態(tài)庫(kù)懶加載方案原理圖:

          代碼拆分組成動(dòng)態(tài)庫(kù)并進(jìn)行懶加載這項(xiàng)技術(shù)多應(yīng)用于業(yè)界大型 App(抖音、Facebook、快手)中,但 QQ 的業(yè)務(wù)復(fù)雜度頗高,直接使用業(yè)界方案無(wú)法滿足我們的需求。

          經(jīng)過一番探索我們找到了一些創(chuàng)新技術(shù)點(diǎn):

          • 1)使用 __attribute__((objc_runtime_visible)) 實(shí)現(xiàn)低成本代碼動(dòng)態(tài)化改造;
          • 2)使用 objc_setHook_getClass 實(shí)現(xiàn)動(dòng)態(tài)化代碼入口收斂,保證了方案穩(wěn)定性。

          最終在 QQ 9 中大規(guī)模的應(yīng)用實(shí)現(xiàn)了對(duì) pre-main 階段的啟動(dòng)耗時(shí)優(yōu)化(這個(gè)技術(shù)方案約貢獻(xiàn)了33%左右的啟動(dòng)總耗時(shí)優(yōu)化數(shù)據(jù)收益),如下圖所示。

          5、 啟動(dòng)速度優(yōu)化實(shí)踐2:線程治理(post-main階段)

          5.1概述

          我們防劣化系統(tǒng)監(jiān)控到主線程搶占的問題越來(lái)越嚴(yán)重,通過 Instruments 查看,我們發(fā)現(xiàn)一些嚴(yán)重的情況下,溫啟動(dòng)過程中主線程有 14%的時(shí)間片處于被其他線程搶占的狀態(tài)。

          Instruments 分析 QQ 啟動(dòng)耗時(shí)圖:

          什么是主線程搶占(Preempted)問題?

          簡(jiǎn)單來(lái)說就是主線程的 CPU 時(shí)間片被其他線程搶占,導(dǎo)致主線程得不到 CPU 資源。隨著搶占問題越來(lái)越嚴(yán)重,也引出一些相關(guān)的問題,例如啟動(dòng)總耗時(shí)也隨著劣化、啟動(dòng)后卡頓、啟動(dòng)耗時(shí)波動(dòng)大、防劣化性能報(bào)告誤判概率增大等。

          為什么會(huì)出現(xiàn)主線程被搶占?

          簡(jiǎn)單來(lái)說有以下幾個(gè)原因:

          • 1)系統(tǒng)調(diào)度行為、系統(tǒng)級(jí)線程(比如 PageIn 線程)搶占;
          • 2)APP 頻繁開辟子線程卻不注意管理子線程的數(shù)量,可能會(huì)出現(xiàn)「線程爆炸」的情況,而且子線程不恰當(dāng)?shù)卦O(shè)置 QoS,會(huì)容易導(dǎo)致主線程被搶占;
          • 3)主線程任務(wù)過重,占用時(shí)間片過長(zhǎng),會(huì)被系統(tǒng)懲罰降級(jí),然后被其他子線程搶占。

          了解了原因以后,我們從以下三個(gè)方面進(jìn)行治理。

          5.2減少子線程的數(shù)量

          手 Q 大部分業(yè)務(wù)廣泛使用 GCD,經(jīng)過查找資料和研究,我們發(fā)現(xiàn)頻繁使用 GCD 的全局隊(duì)列,可能會(huì)導(dǎo)致線程爆炸,原因是當(dāng)子線程在 sleep/wait/lock 狀態(tài)時(shí),會(huì)被 GCD 認(rèn)為是非活躍的狀態(tài),當(dāng)有新的任務(wù)到來(lái)時(shí)可能便會(huì)創(chuàng)建新的線程。

          Apple 工程師、前 GCD 開發(fā)工程師發(fā)表言論:

          蘋果官方建議不要?jiǎng)?chuàng)建大量隊(duì)列,使用 target_queue 設(shè)置隊(duì)列的層級(jí)結(jié)構(gòu),多個(gè)子系統(tǒng)就形成了一個(gè)隊(duì)列的樹狀結(jié)構(gòu),最后隊(duì)列底層使用串行隊(duì)列作為 target_queue 。(詳情請(qǐng)見《Modernizing Grand Central Dispatch Usage - WWDC17》)。

          5.3降低子線程 QoS

          如果全局隊(duì)列 QoS 設(shè)置為 DISPATCH_QUEUE_PRIORITY_DEFAULT,則該任務(wù)的 QoS 將繼承原來(lái)所在隊(duì)列的 QoS (如果原來(lái)隊(duì)列是主隊(duì)列,將從 QOS_CLASS_USER_INTERACTIVE 降低為 QOS_CLASS_USER_INITIATED)。開發(fā)同學(xué)經(jīng)常在主線程將任務(wù)派發(fā)到全局隊(duì)列,并指定 QoS 為 DISPATCH_QUEUE_PRIORITY_DEFAULT ,這將導(dǎo)致存在大量子線程 QoS 為 QOS_CLASS_USER_INITIATED。

          以下是 QoS 優(yōu)先級(jí)排序:

          __QOS_ENUM(qos_class, unsigned int,

            QOS_CLASS_USER_INTERACTIVE = 0x21, // 33

            QOS_CLASS_USER_INITIATED = 0x19, // 25

            QOS_CLASS_DEFAULT = 0x15, // 21

            QOS_CLASS_UTILITY = 0x11, // 17

            QOS_CLASS_BACKGROUND = 0x09, // 9

            QOS_CLASS_UNSPECIFIED = 0x00, // 0

          );

          而實(shí)際開發(fā)中,很多網(wǎng)絡(luò)請(qǐng)求、寫磁盤 I/O,都使用了該 QoS,實(shí)際上是可以通過降低 QoS 來(lái)降低子線程的優(yōu)先級(jí)。

          5.4提高主線程的優(yōu)先級(jí) QoS

          QoS 并不完全等價(jià)于最終的線程優(yōu)先級(jí),主線程優(yōu)先級(jí)范圍為 29~47 。

          為什么運(yùn)行過程中主線程優(yōu)先級(jí)會(huì)變化?

          蘋果官方文檔 《Mach Scheduling and Thread Interfaces》中的 “Why Did My Thread Priority Change? ” 章節(jié)解釋了這個(gè)原因:

          如果線程的運(yùn)行超出了其分配的時(shí)間而沒有被阻塞,則會(huì)受到懲罰甚至被降低優(yōu)先級(jí),這么做的目的就是為了避免高優(yōu)先級(jí)的線程一直搶占系統(tǒng)資源,導(dǎo)致低優(yōu)先級(jí)的線程一直處于饑餓的狀態(tài)。

          如何避免主線程運(yùn)行超出 CPU 分配時(shí)間,而免除降級(jí)懲罰?可以從 RunLoop 層面做減負(fù)。

          App 啟動(dòng)過程開始的第一個(gè) RunLoop,會(huì)執(zhí)行持續(xù)到首屏渲染結(jié)束。而首屏的任務(wù)一般很重,導(dǎo)致 RunLoop 耗時(shí)很長(zhǎng),容易被系統(tǒng)降級(jí)。

          QQ 啟動(dòng)時(shí)第一個(gè) Runloop 耗時(shí)示意圖:

          解決方案是對(duì)第一個(gè) RunLoop 里的任務(wù)做拆分。

          我們的做法是保留必要的全局初始化邏輯在第一個(gè) RunLoop 中,把主 UI 的創(chuàng)建延遲到下一個(gè) RunLoop 里。這樣不僅有效地解決了啟動(dòng)時(shí)主線程被搶占的情況,還能夠加速啟動(dòng)更快看到主頁(yè)面。

          PS:其實(shí)這里還有一些優(yōu)化空間,我們將第一個(gè) RunLoop 的任務(wù)都挪到第二個(gè) RunLoop 了,就又導(dǎo)致第二個(gè) RunLoop 耗時(shí)較大,可以按照此思路繼續(xù)優(yōu)化。

          6、 性能流暢度提升("眾"享絲滑)

          APP的流暢(絲滑),體感上的表現(xiàn)是屏幕內(nèi)容跟隨手指操作即時(shí)變化,每一次操作都即時(shí)地反饋在屏幕上。

          如下圖所示,未開啟高刷幀率時(shí)應(yīng)保證 16.67ms 內(nèi)將用戶操作更新至屏幕上。

          用戶每個(gè)操作,都需經(jīng)歷圖中的4個(gè)步驟,任一步驟時(shí)間過長(zhǎng),都會(huì)無(wú)法及時(shí)更新畫面造成卡頓(來(lái)源:《Advanced Graphics and Animation Performance》)。

          讓 App 做到每 16.67 毫秒更新一次用戶操作很難嗎?

          難,難在這么短的時(shí)間內(nèi) CPU 和 GPU 需要完成很多事情。

          更具體的:

          • 1)屏幕上顯示的內(nèi)容只能在主線程更新(只能單核,無(wú)法利用到手機(jī)的多核 CPU);
          • 2)影響 GPU 的耗時(shí)因素多,展示的界面越復(fù)雜耗時(shí)越多。

          主線程的 16.67 毫秒 - 系統(tǒng)需要的耗時(shí) = 開發(fā)者可用的時(shí)間。如下圖所示,藍(lán)色區(qū)域?yàn)殚_發(fā)者占用的時(shí)間,當(dāng)開發(fā)者使用的時(shí)間過長(zhǎng)即會(huì)造成 hang,即卡頓。

          如上圖所示:

          • 1)紫色區(qū)域:系統(tǒng)接受與處理用戶手勢(shì)操作的耗時(shí);
          • 2)藍(lán)色區(qū)域:開發(fā)者轉(zhuǎn)換用戶操作為屏幕顯示內(nèi)容的耗時(shí);
          • 3)黃色區(qū)域:屏幕展示內(nèi)容的耗時(shí)。

          (來(lái)源:《Explore UI animation hitches and the render loop》)

          如此,想要絲滑就必須做到以下兩點(diǎn):

          • 1)善用多線程編程,盡可能少在主線程上做更新 UI 以外的事情;
          • 2)盡可能讓 GPU 繪制簡(jiǎn)單的界面,減少 GPU 耗時(shí)。

          7、 性能流暢度提升實(shí)踐1:善用多線程編程

          善用多線程編程,盡可能少在主線程上做更新UI以外的事情。

          7.1NT 內(nèi)核架構(gòu)打好基礎(chǔ)

          QQ 9 所采用的 NT Kernel(NT全稱是New Technology,此處向 Windows NT 內(nèi)核致敬),基于盡可能發(fā)揮多核 CPU 能效的理念而誕生,如下圖所示,最大程度將業(yè)務(wù)處理邏輯從負(fù)責(zé) UI 展示的主線程中剝離,且使用異步調(diào)用代替線程鎖,提升效率的同時(shí)降低死鎖的可能。

          NT Kernel 多線程模型:

          此外,NT Kernel 采用 C++ 實(shí)現(xiàn) IM 軟件的核心基礎(chǔ)能力,使其能跨平臺(tái)使用,保證各平臺(tái)的性能體驗(yàn)一致,用戶交互界面則采用各平臺(tái)原生語(yǔ)言實(shí)現(xiàn)。讓用戶感受強(qiáng)勁性能的同時(shí)保證了各平臺(tái)特有的體驗(yàn)。

          NT Kernel 支持多平臺(tái)架構(gòu)圖:

          7.2全量刷新改增量刷新

          在全新 NT 內(nèi)核的加持下,耗時(shí)業(yè)務(wù)邏輯都已經(jīng)挪到子線程,主線程僅剩刷新 UI 的相關(guān)工作。

          那刷新 UI 這個(gè)事兒還有進(jìn)一步優(yōu)化的空間嗎?答案是肯定的,14 年陳的手機(jī) QQ 在屏幕上更新一條新消息,會(huì)將當(dāng)前展示的消息全部刷新一遍,即"全量刷新"機(jī)制。滾動(dòng)時(shí)無(wú)法刷新消息、資源跳變等壞體驗(yàn),都是該機(jī)制導(dǎo)致的。

          為什么滾動(dòng)時(shí)無(wú)法刷新消息?

          并非無(wú)法刷新,而是不能刷新。多余的刷新操作很容易使得 UI 更新無(wú)法在 16.67ms 內(nèi)完成,進(jìn)而誘發(fā)卡頓。

          為什么會(huì)出現(xiàn)資源跳變?

          全量刷新會(huì)觸使屏幕上的所有節(jié)點(diǎn)回收、重用,并且這種重用還是無(wú)序的。如下圖所示,全量刷新后節(jié)點(diǎn)位置會(huì)隨機(jī)發(fā)生改變,例如:尾號(hào)1b400(左圖第2個(gè))的節(jié)點(diǎn)刷新前用于展示2,刷新則展示7(右圖第7個(gè))。

          對(duì)比左右兩張圖的節(jié)點(diǎn)內(nèi)存地址可見,全量刷新后會(huì)出現(xiàn)隨機(jī)變化,并無(wú)規(guī)律可言。

          無(wú)論是靜態(tài)或是動(dòng)態(tài)圖片,都存在磁盤 I/O、解碼等耗時(shí)操作,一般都會(huì)采用異步加載,避免主線程的卡頓。再疊加這種隨機(jī)重用的特性,也就造成了"資源跳變"的表現(xiàn)。

          根據(jù)不同的重用情況會(huì)有以下三種表現(xiàn):

          • 1)恰好是上次所用的節(jié)點(diǎn)或者內(nèi)容恰好相同:相同內(nèi)容賦值,沒有任何變化;
          • 2)沒有相關(guān)動(dòng)/靜圖:內(nèi)容從無(wú)到有,符合預(yù)期;
          • 3)有相關(guān)動(dòng)/靜圖,但與當(dāng)前 Model 的內(nèi)容不一致:出現(xiàn)閃爍。

          如上圖下圖所示:

          • 1)所有異步加載數(shù)據(jù)的元素搭配全量刷新,在未加載完畢前會(huì)展示其他節(jié)點(diǎn)的舊信息;
          • 2)即使刷新時(shí)重置視圖也無(wú)法解決,只是從A->A->B改成A->空->B,依然存在明顯的跳變。

          QQ 9 采用的"增量刷新"就能很好的解決上述兩個(gè)體驗(yàn)問題。

          此外,還有一個(gè)全量刷新無(wú)法實(shí)現(xiàn)的隱藏福利:節(jié)點(diǎn)動(dòng)畫,如下視頻所示。

          實(shí)現(xiàn)增量刷新需要有個(gè)可靠的 Diff 算法,告知系統(tǒng)有變化的節(jié)點(diǎn)是需要執(zhí)行刷新、插入、刪除、移動(dòng)中的哪種操作,一旦給到錯(cuò)誤的信息將會(huì)直接導(dǎo)致 App Cras。敲定算法過程也是一波三折。

          首先,閱讀源碼發(fā)現(xiàn) Android 與 iOS 系統(tǒng)內(nèi)置的 Diff 工具都是采用 Myers 算法實(shí)現(xiàn)的。

          Myers:計(jì)算結(jié)果保存在changes的數(shù)組內(nèi),其中只有insert、remove兩種類型。(來(lái)源:Swift Diffing)

          Myers算法求解過程,通過插入、刪除求源到目的的最短編輯距離(來(lái)源:《AnO(ND) difference algorithm and its variations》)。

          該算法在計(jì)算移動(dòng)時(shí)存在"缺陷",其通過插入+刪除行為推測(cè)移動(dòng),特定場(chǎng)景下移動(dòng)操作會(huì)降級(jí)為插入+刪除。

          比如,先刪除再移動(dòng)就會(huì)轉(zhuǎn)換為刪除+插入,反之則是移動(dòng)+刪除:

          • 1)刪 + 移 → 刪 + 增:數(shù)據(jù)集A:[1, 2, 3, 4, 5]->數(shù)據(jù)集B:[2, 3, 5, 4]。會(huì)刪除1、4,接著插入4;
          • 2)移 + 刪 → 移 + 刪:數(shù)據(jù)集A:[1, 2, 3, 4, 5]->數(shù)據(jù)集B:[1, 2, 4, 3]。會(huì)交換3、4,隨后刪除5。

          經(jīng)過分析,理想的 Diff 算法應(yīng)該具有以下兩種特質(zhì):

          • 1)能夠記錄節(jié)點(diǎn)之間的移動(dòng)關(guān)系,并不是通過插入、刪除的聯(lián)系推斷移動(dòng);
          • 2)具備較低的時(shí)間復(fù)雜度與空間復(fù)雜度。

          對(duì)比行業(yè)方案后,選中論文《A technique for isolating differences between files》中描述的 Heckel Diff 算法。該算法的最優(yōu)、平均、最差復(fù)時(shí)間/空間復(fù)雜度均為 O(m+n),優(yōu)于 Myers 算法的 O((m+n)*d)。其符號(hào)表的實(shí)現(xiàn)方式保證所有移動(dòng)操作均被記錄,不會(huì)再出現(xiàn) Myers 中丟移動(dòng)操作的情況,如下圖所示。

          Heckel算法通過6個(gè)步驟借助符號(hào)表產(chǎn)生新老數(shù)據(jù)之間的Diff信息:

          • 1)PASS1. 建立新數(shù)據(jù)所需新索引數(shù)組(NA)與 Symbol Table 之間的關(guān)系;
          • 2)PASS2. 建立老數(shù)據(jù)所需舊索引數(shù)組(OA)與 Symbal Table 之間的關(guān)系;
          • 3)PASS3. 查找位置沒有變化的節(jié)點(diǎn),更新新舊索引數(shù)組(NA、OA)中的索引信息;
          • 4)PASS4 - PASS5:適用于對(duì)兩個(gè)本文進(jìn)行比較的 Case(存在 Key 值相同的情況),在 QQ 的應(yīng)用場(chǎng)景中不允許出現(xiàn)相同 Key 值的情況,可跳過。感興趣的同學(xué)可以直接查閱論文;
          • 5)PASS6. 根據(jù)現(xiàn)有結(jié)果計(jì)算差異,如圖下圖所示:

          D表示被刪除,U表示沒有變化,4、5之間存在移動(dòng)關(guān)系。

          那么 Heckel 算法是完美的嗎?

          不然,它并沒有考慮冗余的移動(dòng)信息,冗余的移動(dòng)操作會(huì)導(dǎo)致下圖中的動(dòng)畫錯(cuò)亂問題。

          我們?cè)?Heckel 算法的基礎(chǔ)上進(jìn)行改良優(yōu)化,追蹤記錄移動(dòng)操作,區(qū)分出直接移動(dòng)與間接移動(dòng),并將間接移動(dòng)部分進(jìn)行過濾刪除,最終得到滿足 QQ 9 各項(xiàng)指標(biāo)要求的 Diff 算法。

          如下圖示例,ID5 直接移動(dòng)到第一行,ID1-4 都是間接往下移動(dòng)。

          記錄直接移動(dòng)的偏移量(move = insert X + delete Y的偏移量都需要記錄),修正間接/被動(dòng)移動(dòng)的結(jié)果(ID 1-4的移動(dòng))。

          7.3并行預(yù)布局

          異步布局作為業(yè)界的最佳實(shí)踐,自然不能在 QQ 9 上缺席。我們也進(jìn)一步嘗試將異步布局并行化,深挖性能極限。

          首先嘗試了 N 條消息 N 個(gè)線程的方案:用 GCD 派發(fā) N 個(gè)并發(fā)任務(wù),然后用 DispatchGroup 等待這些任務(wù)執(zhí)行完成。通過并行預(yù)布局,將原本一個(gè)線程需要幾十毫秒的預(yù)布局減少到了十幾毫秒。

          這個(gè)方案后來(lái)發(fā)現(xiàn)了 2 個(gè)問題:

          • 1)并行布局 N 條消息的總耗時(shí)還是比串行布局一條消息的耗時(shí)要大得多,受限于 CPU 核心數(shù),代碼中的鎖或其他資源競(jìng)爭(zhēng)導(dǎo)致 N 條消息的參數(shù)準(zhǔn)備和布局計(jì)算沒有能充分的并行;
          • 2)這N條消息的布局任務(wù)分別和 N 個(gè) GCD 任務(wù)一對(duì)一綁定了,GCD 調(diào)度這 N 個(gè)任務(wù)中有任何一個(gè)調(diào)度慢都會(huì)拉長(zhǎng)整個(gè)預(yù)布局的耗時(shí)。

          如上圖所示:

          • 1)充分利用多核CPU的算力;
          • 2)使用并行計(jì)算,布局計(jì)算的總耗時(shí)減少了約76%。

          調(diào)整后的方案如上圖所示,使用了 M 個(gè)執(zhí)行者來(lái)執(zhí)行N條消息的布局任務(wù)(N>=M>0)。當(dāng)前線程(異步布局主線程)來(lái)執(zhí)行 1 個(gè)執(zhí)行者,然后再由 GCD 額外調(diào)度(M-1)個(gè)線程來(lái)執(zhí)行(M-1)個(gè)執(zhí)行者。 首先將待計(jì)算的消息放入一個(gè)隊(duì)列中,每個(gè)執(zhí)行者都會(huì)循環(huán)從待計(jì)算的消息隊(duì)列中取出一條消息執(zhí)行布局計(jì)算,直到待計(jì)算的消息隊(duì)列為空。因?yàn)橄⒌牟季秩蝿?wù)沒有和任何一個(gè)執(zhí)行者綁定,即使有執(zhí)行者較長(zhǎng)時(shí)間沒有被調(diào)度也不會(huì)導(dǎo)致布局計(jì)算遲遲無(wú)法完成,大部分情況下這 M 個(gè)執(zhí)行者會(huì)被 M 個(gè)線程并行執(zhí)行。

          并行布局的總耗時(shí)會(huì)隨著并發(fā)線程的增加而減少,當(dāng)增加到5以后耗時(shí)就基本沒有怎么減少了。

          看上去目前布局計(jì)算的工作已經(jīng)從主線程挪走了,現(xiàn)實(shí)是很多時(shí)候計(jì)算出來(lái)的坐標(biāo)與大小并沒有與屏幕的像素點(diǎn)大小吻合,此時(shí)系統(tǒng)會(huì)在主線程再做一次“像素對(duì)齊”。在“異步布局”時(shí)也不能忽略該細(xì)節(jié),才能確確實(shí)實(shí)減少主線程的負(fù)擔(dān),如下圖所示。

          OLED屏幕的1個(gè)像素R:G:B比例為1:2:1,顯示時(shí)DDIC(Display Driver IC,顯示驅(qū)動(dòng)芯片)會(huì)進(jìn)行次像素渲染從其他像素借元素使顯示更飽滿。但代碼并不能直接控制該行為,系統(tǒng)需要保證提交的內(nèi)容與屏幕像素完全對(duì)齊,即不能出現(xiàn)類似使用0.5個(gè)像素的情況。

          標(biāo)黃區(qū)域?yàn)樽鴺?biāo)、大小結(jié)果與屏幕像素未對(duì)齊:

          其他的優(yōu)化還有:智能預(yù)加載、消息回收、圖片資源異步解碼等。

          如下圖所示:根據(jù)屏幕比例得到一級(jí)緩存 display ,二級(jí)緩存 preload ,超出的部分則被回收釋放。

          8、 性能流暢度提升實(shí)踐2:減少GPU耗時(shí)

          減少GPU耗時(shí),盡可能讓GPU繪制簡(jiǎn)單的界面。

          除了布局可以異步計(jì)算,復(fù)雜的圖像也能使用"異步渲染"的方式降低 GPU 的耗時(shí)。特別是面對(duì)需要疊加裁剪的圖形時(shí), GPU 的繪制任務(wù)無(wú)法在一個(gè) Frame 內(nèi)完成,就需要再額外開辟一個(gè) Frame Buffer 進(jìn)行繪制,并在全部完成后將兩個(gè) Buffer 的內(nèi)容進(jìn)行合成,這被稱作“離屏渲染”。離屏渲染對(duì)于性能的損耗非常大,主要在于 GPU 的上下文切換所需的開銷很大,需要清空當(dāng)前的管線和柵欄。原話在這:A Performance-minded take on iOS design | Lobsters

          對(duì)于這種情況,蘋果的工程師給出的建議是用 CPU 繪制來(lái)給 GPU 分擔(dān)一部分工作。如下圖所示。

          標(biāo)黃區(qū)域?yàn)镚PU離屏渲染,不可否認(rèn)GPU的off-screen比CPU的off-screen代價(jià)高很多。在無(wú)法避免mask的場(chǎng)景下,使用多核CPU進(jìn)行異步渲染性能更好。

          我們?cè)阡秩鞠r(shí)利用了多核 CPU 進(jìn)行異步渲染,降低 GPU 部分的耗時(shí)。

          這里面臨的難點(diǎn)在于:在可快速滑動(dòng)更新的列表場(chǎng)景使用時(shí)會(huì)出現(xiàn)"閃白"的問題(如著名第三方開源框架 YYKit 也存在此類問題),我們通過 LRU 緩存+增量刷新的方式很好的解決了此問題。

          9、 性能流暢度提升效果展示

          基于上述 CPU 與 GPU 維度的各項(xiàng)優(yōu)化,我們?cè)谙?Tab 上實(shí)現(xiàn)了國(guó)內(nèi)頭部同類應(yīng)用目前也不具備的滾動(dòng)中實(shí)時(shí)接收消息的能力,且不會(huì)出現(xiàn)卡頓。

          此外,也擴(kuò)展了老版本 150 個(gè)會(huì)話的限制,與聊天界面一致以分頁(yè)的形式加載用戶所有的會(huì)話節(jié)點(diǎn),如下所示。

          滾動(dòng)中接受消息,且不卡頓:

          進(jìn)入群、好友聊天界面的速度也得到了質(zhì)的提升,在加快進(jìn)入動(dòng)畫的同時(shí),依然能夠保證即刻就能看到最新的聊天內(nèi)容。如下圖所示(同一個(gè)帳號(hào)進(jìn)入同一個(gè)聊天頁(yè)面)。左邊是優(yōu)化前的效果,聊天頁(yè)面都快全部展示了,內(nèi)容還在加載中;右邊是優(yōu)化后效果,聊天頁(yè)面只展示了一點(diǎn)點(diǎn),就已經(jīng)能看到發(fā)送方頭像和消息內(nèi)容了。

          進(jìn)入聊天頁(yè)面加載速度對(duì)比圖(左為優(yōu)化前,右為優(yōu)化后):

          除了進(jìn)入速度的提升,聊天內(nèi)容翻頁(yè)的速度也達(dá)到了業(yè)內(nèi)頂尖水平:超越國(guó)內(nèi)頭部同類應(yīng)用,對(duì)標(biāo) Telegram。不論用戶有多少消息,都能夠通過不斷上拉看到,并且用戶感知不到 loading 態(tài)。

          聊天頁(yè)面優(yōu)化前:

          聊天頁(yè)面優(yōu)化后:

          10、 防劣化系統(tǒng)

          打江山易,守江山難。防劣化是所有達(dá)到一定規(guī)模的技術(shù)團(tuán)隊(duì)都會(huì)頭疼的問題,面對(duì)復(fù)雜的業(yè)務(wù)和技術(shù)債,手 Q 團(tuán)隊(duì)投入了 3 年的時(shí)間迭代優(yōu)化,現(xiàn)在手 Q 的防劣化系統(tǒng)已經(jīng)達(dá)到了業(yè)界先進(jìn)水平。

          作為手 Q 質(zhì)量的守門員,我們將其命名為 Hodor(Hold the door):

          • 1)防劣化目標(biāo):提前發(fā)現(xiàn)部分主路徑問題,通過門禁防止性能劣化。
          • 2)主干合流門禁:對(duì)于較穩(wěn)定的性能指標(biāo),合流前自動(dòng)檢查。
          • 3)日常自動(dòng)提單:針對(duì)偶現(xiàn)的性能問題,開發(fā)階段提前發(fā)現(xiàn)。
          • 4)性能數(shù)據(jù)看板:常態(tài)化詳細(xì)數(shù)據(jù)看板,上帝視角觀測(cè)性能。
          • 5)告警機(jī)器人:自定義各性能維度告警規(guī)則,第一時(shí)間反饋問題。

          具體是:

          • 1)整體方案是基于 Instruments 動(dòng)態(tài)追蹤技術(shù)采集 diagnostic 診斷數(shù)據(jù);
          • 2)xctrace 自動(dòng)解析 trace 文件,翻譯堆棧精準(zhǔn)歸因;
          • 3)每次提交構(gòu)建均執(zhí)行防劣化檢測(cè),精準(zhǔn)定位問題;
          • 4)還有數(shù)據(jù)可視化看板 + 自動(dòng)提單派發(fā),將質(zhì)量左移到開發(fā)階段。

          最終實(shí)現(xiàn)了性能報(bào)告、數(shù)據(jù)分析、智能調(diào)度、提單告警、設(shè)備管理、用例管理等一系列能力。

          一圖以蔽之,防劣化系統(tǒng)方案簡(jiǎn)介:

          PS:Xcode 12 開始提供了 xctrace,其 Release Notes 中解決的很多 issue 也來(lái)自于手 Q 團(tuán)隊(duì)在防劣化開發(fā)過程中發(fā)現(xiàn)與反饋。在性能優(yōu)化方面 QQ 與 Apple 性能團(tuán)隊(duì)交流緊密,大家也會(huì)加班克服中美時(shí)差。

          整個(gè)手 Q 防劣化系統(tǒng)上線以來(lái),有效地保證了開發(fā)主干的穩(wěn)定性,也檢測(cè)到了大量的性能和崩潰問題,同時(shí)攔住了很多新需求引入的性能問題。

          防劣化成果圖:

          目前 Hodor 已經(jīng)覆蓋數(shù)十個(gè)場(chǎng)景,并落地 iOS/Android/Windows/macOS/Linux 五個(gè)平臺(tái)。

          11、 在最新QQ9中的生產(chǎn)實(shí)踐效果

          經(jīng)過上述全方位優(yōu)化:QQ 9 在各場(chǎng)景的性能都較歷史版本較大的提升,如下圖所示。

          使用蘋果官方的工具:Xcode Organizer 可以看到 QQ 9在流暢度上較之前的版本 50 分位提升35%,卡頓率降低48%,啟動(dòng)耗時(shí)降低40%。如下圖所示。

          12、 本文小結(jié)

          本文我們介紹了 QQ 9 絲滑背后的技術(shù)實(shí)現(xiàn),從啟動(dòng)速度,頁(yè)面刷新,差異算法,預(yù)加載和回收,異步布局和渲染等方面介紹了我們?cè)谛阅芊矫孀龅娜鞒虄?yōu)化,并介紹了幾個(gè)用戶體驗(yàn)提升的場(chǎng)景表現(xiàn)。

          其實(shí)技術(shù)領(lǐng)域深入復(fù)雜,每一項(xiàng)優(yōu)化點(diǎn)都可以單獨(dú)拎出來(lái)好好地展開說明,因?yàn)槠鶈栴},只能留到以后慢慢和大家分享。

          希望 QQ 技術(shù)團(tuán)隊(duì)做的這些打磨,可以給用戶帶來(lái)切實(shí)的體驗(yàn)提升,也希望 QQ 能越來(lái)越好,因?yàn)槲覀兠恳晃灰彩菆?jiān)持使用 QQ 的 5 億分之一。

          13、 相關(guān)資料

          [1] 大型IM工程重構(gòu)實(shí)踐:企業(yè)微信Android端的重構(gòu)之路

          [2] 企業(yè)微信針對(duì)百萬(wàn)級(jí)組織架構(gòu)的客戶端性能優(yōu)化實(shí)踐

          [3] 微信團(tuán)隊(duì)分享:詳解iOS版微信視頻號(hào)直播中因幀率異常導(dǎo)致的功耗問題

          [4] 騰訊技術(shù)分享:Android版手機(jī)QQ的緩存監(jiān)控與優(yōu)化實(shí)踐

          [5] 騰訊技術(shù)分享:Android手Q的線程死鎖監(jiān)控系統(tǒng)技術(shù)實(shí)踐

          [6] 全面解密新QQ桌面版的Electron內(nèi)存優(yōu)化實(shí)踐

          [7] 移動(dòng)端IM實(shí)踐:iOS版微信界面卡頓監(jiān)測(cè)方案

          [8] 微信團(tuán)隊(duì)原創(chuàng)分享:Android版微信的臃腫之困與模塊化實(shí)踐之路

          [9] 微信Windows端IM消息數(shù)據(jù)庫(kù)的優(yōu)化實(shí)踐:查詢慢、體積大、文件損壞等

          [10] 微信團(tuán)隊(duì)分享:微信支付代碼重構(gòu)帶來(lái)的移動(dòng)端軟件架構(gòu)上的思考

          [11] 微信客戶端團(tuán)隊(duì)負(fù)責(zé)人技術(shù)訪談:如何著手客戶端性能監(jiān)控和優(yōu)化

          [12] 抖音技術(shù)分享:飛鴿IM桌面端基于Rust語(yǔ)言進(jìn)行重構(gòu)的技術(shù)選型和實(shí)踐總結(jié)

          [13] 阿里技術(shù)分享:閑魚IM基于Flutter的移動(dòng)端跨端改造實(shí)踐

          [14] QQ設(shè)計(jì)團(tuán)隊(duì)分享:新版 QQ 8.0 語(yǔ)音消息改版背后的功能設(shè)計(jì)思路

          14、更多鵝廠技術(shù)文章匯總

          1. 微信朋友圈千億訪問量背后的技術(shù)挑戰(zhàn)和實(shí)踐總結(jié)
          2. 騰訊技術(shù)分享:騰訊是如何大幅降低帶寬和網(wǎng)絡(luò)流量的(圖片壓縮篇)
          3. IM全文檢索技術(shù)專題(二):微信移動(dòng)端的全文檢索多音字問題解決方案
          4. 微信團(tuán)隊(duì)分享:iOS版微信的高性能通用key-value組件技術(shù)實(shí)踐
          5. 微信團(tuán)隊(duì)分享:iOS版微信是如何防止特殊字符導(dǎo)致的炸群、APP崩潰的?
          6. 微信團(tuán)隊(duì)分享:微信Android版小視頻編碼填過的那些坑
          7. IM全文檢索技術(shù)專題(一):微信移動(dòng)端的全文檢索優(yōu)化之路
          8. 企業(yè)微信客戶端中組織架構(gòu)數(shù)據(jù)的同步更新方案優(yōu)化實(shí)戰(zhàn)
          9. 微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解
          10. 微信“紅包照片”背后的技術(shù)難題
          11. 移動(dòng)端IM實(shí)踐:iOS版微信的多設(shè)備字體適配方案探討
          12. 騰訊信鴿技術(shù)分享:百億級(jí)實(shí)時(shí)消息推送的實(shí)戰(zhàn)經(jīng)驗(yàn)
          13. IPv6技術(shù)詳解:基本概念、應(yīng)用現(xiàn)狀、技術(shù)實(shí)踐(上篇)
          14. 騰訊技術(shù)分享:GIF動(dòng)圖技術(shù)詳解及手機(jī)QQ動(dòng)態(tài)表情壓縮技術(shù)實(shí)踐
          15. 微信團(tuán)隊(duì)分享:Kotlin漸被認(rèn)可,Android版微信的技術(shù)嘗鮮之旅
          16. 社交軟件紅包技術(shù)解密(一):全面解密QQ紅包技術(shù)方案——架構(gòu)、技術(shù)實(shí)現(xiàn)等
          17. 社交軟件紅包技術(shù)解密(四):微信紅包系統(tǒng)是如何應(yīng)對(duì)高并發(fā)的
          18. 社交軟件紅包技術(shù)解密(十):手Q客戶端針對(duì)2020年春節(jié)紅包的技術(shù)實(shí)踐
          19. 微信團(tuán)隊(duì)分享:極致優(yōu)化,iOS版微信編譯速度3倍提升的實(shí)踐總結(jié)
          20. IM“掃一掃”功能很好做?看看微信“掃一掃識(shí)物”的完整技術(shù)實(shí)現(xiàn)
          21. 微信團(tuán)隊(duì)分享:微信支付代碼重構(gòu)帶來(lái)的移動(dòng)端軟件架構(gòu)上的思考
          22. IM開發(fā)寶典:史上最全,微信各種功能參數(shù)和邏輯規(guī)則資料匯總
          23. 微信團(tuán)隊(duì)分享:微信直播聊天室單房間1500萬(wàn)在線的消息架構(gòu)演進(jìn)之路
          24. 企業(yè)微信的IM架構(gòu)設(shè)計(jì)揭秘:消息模型、萬(wàn)人群、已讀回執(zhí)、消息撤回等
          25. IM全文檢索技術(shù)專題(四):微信iOS端的最新全文檢索技術(shù)優(yōu)化實(shí)踐
          26. 微信團(tuán)隊(duì)分享:微信后臺(tái)在海量并發(fā)請(qǐng)求下是如何做到不崩潰的
          27. 微信Windows端IM消息數(shù)據(jù)庫(kù)的優(yōu)化實(shí)踐:查詢慢、體積大、文件損壞等
          28. 微信技術(shù)分享:揭秘微信后臺(tái)安全特征數(shù)據(jù)倉(cāng)庫(kù)的架構(gòu)設(shè)計(jì)
          29. IM跨平臺(tái)技術(shù)學(xué)習(xí)(九):全面解密新QQ桌面版的Electron內(nèi)存優(yōu)化實(shí)踐
          30. 企業(yè)微信針對(duì)百萬(wàn)級(jí)組織架構(gòu)的客戶端性能優(yōu)化實(shí)踐
          31. 揭秘企業(yè)微信是如何支持超大規(guī)模IM組織架構(gòu)的——技術(shù)解讀四維關(guān)系鏈
          32. 微信團(tuán)隊(duì)分享:詳解iOS版微信視頻號(hào)直播中因幀率異常導(dǎo)致的功耗問題
          33. 微信團(tuán)隊(duì)分享:微信后端海量數(shù)據(jù)查詢從1000ms降到100ms的技術(shù)實(shí)踐
          34. 大型IM工程重構(gòu)實(shí)踐:企業(yè)微信Android端的重構(gòu)之路
          35. IM技術(shù)干貨:假如你來(lái)設(shè)計(jì)微信的群聊,你該怎么設(shè)計(jì)?
          36. 微信團(tuán)隊(duì)分享:來(lái)看看微信十年前的IM消息收發(fā)架構(gòu),你做到了嗎
          37. 長(zhǎng)連接網(wǎng)關(guān)技術(shù)專題(十一):揭秘騰訊公網(wǎng)TGW網(wǎng)關(guān)系統(tǒng)的技術(shù)架構(gòu)演進(jìn)


          (本文已同步發(fā)布于:http://www.52im.net/thread-4656-1-1.html



          作者:Jack Jiang (點(diǎn)擊作者姓名進(jìn)入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時(shí)通訊開發(fā)交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時(shí)是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級(jí)移動(dòng)端即時(shí)通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處(也可前往 我的52im.net 找到我)。


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 双峰县| 冀州市| 大安市| 新泰市| 利川市| 长垣县| 上栗县| 屏东市| 锦屏县| 阿鲁科尔沁旗| 安国市| 长垣县| 龙门县| 武胜县| 大庆市| 吉木乃县| 崇明县| 冷水江市| 建瓯市| 蚌埠市| 安泽县| 二连浩特市| 湘潭市| 青川县| 莒南县| 安吉县| 民乐县| 温宿县| 富阳市| 皮山县| 凤庆县| 清水河县| 思茅市| 准格尔旗| 鄂州市| 囊谦县| 西城区| 乳山市| 武鸣县| 会同县| 隆子县|