本文由CocoaChina譯者小袋子(博客)翻譯
原文:Optimizing Facebook for iOS start time
穩定的指標
實現最好的性能度量標准和相應的目標,鼓舞我們專注於提升應用的品質,並且我們相信這將會產生很大的影響。度量必須易懂、經得起推敲,並且需要精確地捕捉到將要被優化的體驗。對基於性能的度量,我們已經發現在使用應用過程中,最好是使用那些被捕捉到感知的交互。理想情況下,這些度量應該和一個通過基礎設施的單一執行通道有一對一的聯系。對於應用程序的啟動,確定用於衡量的關鍵位置是一個挑戰。這需要采取幾次迭代去簡化我們的測量和移除邊界問題。
應用啟動是一個特別不固定的概念,因為現在存在很多種應用啟動的方式。應用可以在後台或者前台啟動,甚至可以在後台啟動,但是在完成初始化之前轉換為前台。你可以通過點擊一條通知或者通過一個 URL 打開應用。Facebook 應用甚至可以通過其他應用來打開,因為他們需要通過 Facebook 來實現第三方登錄。在現實場景中,主要的交互還是最直接的方式:你點擊桌面的應用圖標,然後跳轉啟動。因而,我們選擇這個作為應用啟動的入口。
當啟動入口明確之後,我們必須去計算出何時應用啟動是完成的。同樣地,我們觀察用戶的使用模式,發現用戶喜歡打開應用(首先跳轉到新聞摘要),然後等待摘要的加載。我們斷定“摘要完成加載”是應用啟動一個很好的終點。我們采取了一些微調使得這個終點契合用戶的使用情況。我們可以通過重復地觀察應用的啟動,圍繞度量標准來提高應用的性能。
一旦確定了我們認為有代表性的啟動入口和終點,我們把啟動問題分解成兩種類型:
冷啟動。指的是當應用還沒准備好運行時,我們必須加載和構建整個應用。這包括設置屏幕底部的分欄菜單,確保用戶是否被合適地登錄,以及處理其他更多的事情。“引導”程序是在applicationDidFinishLaunching:withOptions:方法中開始的。
熱啟動。指的是應用已經運行但是在後台被掛起(比如用戶點擊了 home 健),我們只需要知道在何時應用進入後台。在這種情況下,我們的應用通過 applicationWillEnterForeground: 接收到前台的事件,緊接著應用恢復。
我們決定主要優化冷啟動,主要有兩個原因。
首先,冷啟動其實是包括熱啟動的(冷啟動初始化應用並獲得摘要;熱啟動只獲得摘要),所以有更多的地方需要優化和微調。
其次,冷啟動需要做額外的初始化工作,所以相較而言更慢,導致需要更長的啟動等待時間。
優化冷啟動體驗
我們把冷啟動問題分解成三個階段,進而我們可以有針對性地解決。每個階段都有一些列變數和挑戰。
請求時間:從應用啟動到摘要請求離開設備(譯者:應該是向服務器發送URL請求算結束時間)的時間。
網絡時間:從摘要請求離開設備到服務器響應返回的時間。
響應處理時間:從響應返回到新數據展示在屏幕的時間。
我們直觀上認為冷啟動性能主要被網絡請求和響應處理影響了。這個結論是由於我們假定我們在客戶端花的時間比較少,並且我們設法讓請求的獲取更加快速。然而,當我們用 instrument 去檢測時,我們發現數據非常出人意料。它展現出了完全不同的結果,我們發現摘要請求花了大部分時間。另外,響應的處理時間也非常短。因此,我們重新把優化的焦點放在初始化階段。
Feed請求發送的初始化
所以為什麼這個階段花費了那麼多時間呢?很多 iOS 應用並沒有這樣一個問題——他們在那個階段並沒有很多工作需要做,除了初始化視圖控制器和發送網絡請求。然而,對於 Facebook 來說,大部分時間被用來開始的時候去設置不同功能塊。下面是我們應用中的主要功能塊的流程概覽。
這看起來好像是很復雜的應用啟動設置。但需要重視的是,這些功能塊對於 Facebook 應用來說是非常重要的提升,可以提高應用體驗,並且使得工程師能夠在不同的應用規模下更快地開發。
正如我們所關注的這個流程,我們通過優化獨立的部分獲得了一些主要的成果。然而,由於未來支持新特性的初始化以及額外提供支持的基礎設施,這些成果會慢慢地抵消掉。這使得我們重新考慮如何去解決問題。但我們重新開始,我們認為這個階段的目標是簡單地發送摘要的網絡請求。但是為什麼摘要請求發出去得這麼慢?這是由於很多依賴被添加到摘要的初始化中了。然而,他們並不都是必要的 — 對於摘要請求來說,最少的需要一個有效的驗證 token 以及摘要光標(新聞摘要的位置)。因此,我們減少了摘要請求的依賴,讓它逐漸地更加接近應用的啟動。這允許應用的剩余部分在摘要響應的同時進行初始化。由於這些重構,我們獲得了顯著的收益。
網絡和服務器時間
根據我們在第一階段的經驗,我們繼續把這個階段分解成更小的部分。網絡請求/響應看起來像這樣:
我們注意到,一旦請求正在排隊,發送請求出去之後就有一個時間間隔。這很好解釋 — 在冷啟動中,網絡連接並不是一個開放的、安全的 TCP 連接。一個連接的建立需要三次握手,平均為幾百毫秒。當摘要請求第一次發送時,無法避免要花掉這些時間。長遠來看,這可以通過緩存 SSL 證書來解決。但是再次強調,我們退回來的目的並不是為了發送 TCP 請求,而是為了從服務器通過任何可能的方式獲得請求信息。
我們提出了一個創造性的解決方案 — UDP 啟動。本質上,我們在通過 TCP 發送摘要請求時,先發送一個編碼過的包含摘要請求的 UDP 包到服務器。這樣做的目的是喚醒服務器更早地去獲取和緩存數據。當真正的摘要請求通過 TCP 到達時,服務器只需見到地從緩存內容中構造出響應,並發回客戶端。這個技術使得我們可以減少幾百毫秒的耗時。
當我們持續深入研究服務器端時,我們開始嘗試使用 層-取(story-fetching)策略。過去我們已經做了一批摘要請求的 3+7 層。原因很簡單:下載次數和被下載的層成正比。因此,把請求分割成兩塊,允許開始的三層先進來,其余的七個隨後進來。通過提升我們的基礎設施,我們已經能夠升級為 1+1+X 策略,這已經接近於流了。這樣就減少了服務器必須處理第一層的時間,並且能夠減少下載的時間,使得可以在最快的時間內與用戶交互。通過這樣的努力,這樣我們又減少了幾百毫秒的耗時。
Feed響應處理
正如在前面提到的那樣,我們以為在啟動時會在這裡花費大量的時間。但是這個想法被證明是錯誤的。更加使人好奇的是,我們注意到時間並沒有花在處理和加工層上面。時間被花在運行應用服務和競爭資源上面。我們注意到這是我們優化網絡和服務器時間的副作用,因為摘要請求返回得太早了。盡管大多數的服務是不重要的。因此,我們開發出一個簡單的機制去序列化這些工作直到應用完成啟動,並且使用先進先出的方式去執行。這樣可以用更少的連接去處理所有層,大大地減少了獲得響應和展示在屏幕之間的時間。
總結
很難理解我們在過去幾個月走了多遠。總之,在一對一的比較中,我們發現我們成功地優化了一秒多的耗時。
優化這個特殊的交互是一個長期的過程,需要建立一個穩定的度量,這個度量必須是易懂的、符合真實世界性能特征,此外要不斷地重新思考問題,以提出創新的解決方案。我們希望這可以幫助使用 Facebook 的人有更好的、令人愉悅的用戶體驗。
你也可以看看 Greg Moeck在 2015 年的演講。
本文僅用於學習和交流目的,轉載請注明文章譯者、出處以及本文鏈接。
感謝博文視點對本期翻譯活動的支持。