天天看點

手淘雙十一系列(一) | 521 性能優化項目揭秘

該文章來自阿裡巴巴技術協會(ata)精選集 

億萬使用者都會在雙十一這一天打開手機淘寶,高興地在會場頁面不斷浏覽,面對琳琅滿目的商品圖檔,搶着添加購物車,下單付款。為了讓使用者更順暢更友善地實作這一切,做到“如絲般順滑”,雙十一前夕手機淘寶成立了“521”(我愛你)性能優化項目,在日常優化基礎之上進行三個方面的專項優化攻關,分别是:

1)h5頁面的一秒法則;

2)啟動時間和頁面幀率提升20%;

3)android記憶體占用降低50%。

優化過程中遇到的困難,思考後找尋的方案,實施後提取的經驗将會在下面這篇文章中詳細地介紹給大家。

第一章 一秒法則的實作

“1s法則”是面向web側,h5鍊路上加載性能和體驗方向上的一個名額,具體指:

“強網”(4g/wifi)下,1秒完全完成頁面加載,包括首屏資源,可看亦可用;

3g下1秒完成首包的傳回

2g下1秒完成建連。

在移動網絡環境下,http請求和資源加載與有線網絡或者pc時代相比有着本質差別,尤其是在2g/3g網絡下,往往一個資源請求建連的時間都會是整個request-response流程裡面的大頭,一些小資源上拖累效應尤其明顯。例如一個1k的圖檔,即使在10k/s 的極慢網速下,理論上0.1秒可下載下傳完畢,但由于建立連接配接的巨大消耗,這樣一個請求會要耗上好幾秒。

 僅僅“建連”這一個點,就能說明移動時代的web側性能優化和pc時代目标和方式都相去甚遠,要求我們必須從更底層,更細緻的去抓,才能取得看起來相對有效的結果。

15年初的性能情況

平均loadtime-wifi

平均loadtime - 4g

平均loadtime - 2g

3.35s

3.84s

14.34s

 可以看到優化前,平均時間很難接近1秒。為了實作優化目标,在技術和實施抓手層面,由底層往上,做了四方面事情:

網絡節點:httpdns優化

建連複用:ssl化,spdy建連高複用

容器層面:離線化和預加載方案

前端元件:請求控制,域名收斂,圖檔庫,前端性能checklist

dns解析想必大家都知道,在傳統pc時代dns lookup基本在幾十ms内。而我們通過大量的資料采集和真實網絡抓包分析(存在dns解析的請求),dns的消耗相當可觀,2g網絡大量5-10s,3g網絡平均也要3-5s。

針對這種情況,手淘開發了一套httpdns-面向無線端的域名解析服務,與傳統走udp協定的dns不同,httpdns基于http協定。基于http的域名解析,減少域名解析部分的時間并解決dns劫持的問題。

手淘httpdns服務在啟動的時候就會對白名單的域名進行域名解析,傳回對應服務的最近ip(各營運商),端口号,協定類型,心跳等資訊。

優點

1.防止域名劫持

傳統dns由local dns解析域名,不同營運商的local dns有不同的政策,某些local dns可能會劫持特定的域名。采用httpdns能夠繞過local dns,避免被劫持;另外,httpdns的解析結果包含hmac校驗,也能夠防止解析結果被中間網絡裝置篡改。

2.更精準的排程

對域名解析而言,尤其是cdn域名,解析得到的ip應該更靠近用戶端的地區和營運商,這樣才能有更快的網絡通路速度。然而,由于營運商政策的多樣性,其推送的local dns可能和用戶端不在同一個地區,這時得到的解析結果可能不是最優的。httpdns能夠得到用戶端的出口網關ip,進而能夠更準确地判斷用戶端的地區和營運商,得到更精準的解析結果。

3.更小的解析延遲和波動

在2g/3g這種移動網絡下,dns解析的延遲和波動都比較大。就單次解析請求而言,httpdns不會比傳統的dns更快,但通過httpdns用戶端sdk的配合,總體而言,能夠顯著降低解析延遲和波動。httpdns用戶端sdk有幾個特性:預解析、多域名解析、ttl緩存和異步請求。

4.額外的域名相關資訊

傳統dns的解析結果隻有ip,httpdns的解析結果采用json格式,除了ip外,還支援其它域名相關的資訊,比如端口、spdy協定等。利用這些額外的資訊,app可以啟用或停止某個功能,甚至利用httpdns來做灰階釋出,通過httpdns控制灰階的比例。

出于安全目的,淘寶實作了全站ssl化。本身和h5鍊路性能優化沒有直接的關系,但是從資料層面看,ssl化之後的資源加載耗時都會略優于普通的http連接配接。

有讀者會有疑惑,ssl化之後每個域名首次請求會額外增加一個“ssl握手”的時間,dns建連也會比http的狀态下要長,這是不可避免的,但是為什麼一次完整的requestrespone 流程耗時會比http狀态下短呢?

合理的解釋是:ssl化之後,spdy可以預設開啟,spdy協定下的傳輸效率和建連複用效益将最大化。spdy協定下,資源并發請求數将不再受浏覽器webview的并發請求數量限制,并發100+都是可能的。

同時,在保證了域名收斂之後,同樣域名下的資源請求将可以完全複用第一次的dns建連和ssl握手,是以,僅在第一次消耗的時間完全可以被spdy後續帶來的資源傳輸效率,并發能力,以及連接配接複用度帶來的收益補回來。甚至理論上,越複雜的頁面,資源越多的情況,ssl化+spdy之後在性能上帶來的收益越大。

 收益最明顯,實作中遇到困難最多的就是離線化或者說資源預加載的方案。預加載方案是為了在使用者通路h5之前,将頁面靜态資源(html/js/css/img...)打包預加載到用戶端;使用者通路h5時,将網絡io攔截并替換為本地檔案io;進而實作h5加載性能的大幅度提升。

手淘雙十一系列(一) | 521 性能優化項目揭秘

手淘實作要比上面的通用示意圖複雜:因為android和ios安裝包已經很大,是以預加載zip包(以下簡稱“包”)都是從伺服器端下載下傳到用戶端;本地需要記錄整體包狀态,并在合适的時機與伺服器通信并交換狀态資訊。在包釋出更新的過程中要注意,本地版本和服務端最新包之間的差量同步,必要的網絡判斷,wifi下才下載下傳等。

面對億級uv,并且在伺服器資源很有限的情況下搞定這個流程,需要借助cdn來扛住壓力,實際上cdn扛住了約98%的流量。需要注意的是預加載實際上也是一種緩存,更新比h5稍慢一些,主要受幾個因素影響:推送到達率(使用者是否線上,使用者所在網絡品質),總控,服務端政策等,是以需要通過推拉結合的觸發政策并優化下載下傳包的體積(增量包)來提升到達率。

除了優化到達率,手淘還做了url解cdn combo後再映射的優化工作,若 url 是 combo url,那麼會對 url 解 combo,解析出其中包含的資源。然後嘗試從本地讀取包含的資源,如果所有資源都在本地存在,那麼将本地檔案内容拼裝為一份完整檔案并傳回;否則 url 直接走線上,不做任何操作。

提升到達率和解cdn combo再映射,這兩個容器側對于離線化方案的優化對于本次h5鍊路上整體性能的提升有着至關重要的意義。

嚴格執行性能方面的checklist,主要有三個點:

圖檔資源域名全部收斂到gw.alicdn.com;

前端圖檔庫根據強弱網和裝置分辨率做适配;

首屏資料合并請求為一個。

在執行中,性能的檢查和校驗一定要納入到釋出階段,否則就不是一個合理的流程。性能的工具和校驗一定應該是工程化,研發流程裡面的一部分,才能夠保障性能自動化,低成本,不退化。

通過以上優化方案,h5頁面的平均loadtime在wifi,4g下均如期進入1秒,3g和2g也有80%多達成1s法則的目标。

第二章 啟動時間和頁面幀率20%的提升

很多app都會遇到以下幾個常見的性能問題:啟動速度慢;界面跳轉慢;事件響應慢;滑動和動畫卡頓。

手機淘寶也不例外。我們分為兩部分來做,第一部分是啟動階段優化,目的解決啟動任務繁多,缺乏管控的問題,減少啟動和首頁響應時間。第二部分是針對各個界面做優化,提升界面跳轉時間和滑動幀率,解決卡頓問題。雙十一性能優化目标之一就是将啟動時間和頁面幀率在原有基礎上繼續優化提升20%,接下來就從這兩部分的優化過程來做一一介紹。

一、啟動階段的優化

手機淘寶作為阿裡無線的航母,接入的業務bundle超過100個,啟動初始化任務超過30個,這些任務缺少管控和性能監控。

那麼首要任務就是:

所有的初始化任務可以用兩個次元來區分:

任務必要性:有些任務是應用啟動所必需的,比如網絡、主容器;有些任務則不是必需的,僅僅實作單個業務功能,甚至是為了業務自身體驗和性能而考慮在啟動階段提前執行,其合理性值得推敲。

任務獨立性:将應用的架構簡單分成基礎庫、中間件、業務三層,這三層中業務層最為龐大,其初始化任務也最多。對于中間件來說,其初始化可能依賴于另外一個中間件。但對于一個獨立的業務子產品來說,其初始化任務應該也具有獨立性,不存在跟其他業務子產品依賴關系。

啟動階段任務管理機制包含了如下幾方面的内容

任務可并行

既然很多初始化任務是獨立的,那麼并行執行可以提高啟動效率。

任務可串行

雖然我們期望所有初始化任務都互相獨立,但是在實際中不可避免會存在互相依賴的初始化任務。為了支援這種情況,我們設計任務的異步串行機制,這裡主要借鑒了前端的promise思想實作。

任務可插拔

面對這麼多不同優先級的初始化任務,任何一個出現異常都會導緻應用不能啟動,給穩定性帶來嚴重挑戰。是以我們設計了可插拔機制,當某一項初始化任務出現問題時能夠跳過該任務,進而不影響整個應用的啟動使用。這裡我們根據初始化任務的必要性做了區分,隻有非必要的初始化任務才會應用可插拔的特性,這也是為了防止出現不執行一個必要的初始化任務導緻應用啟動使用出現問題。

任務可配置

在ios上通過plist指定每一項啟動任務, 其中字段optional表示該項是否是必需的,當之前運作出現crash或者異常時,若值為yes則可以不執行該項。

有了任務管理機制,并引入懶加載的理念,可以持續地合理有效管控啟動階段的各項初始化任務,是大型app必不可少的環節。

性能優化前,初始化代碼都在主線程中執行,為了啟動性能已将部分初始化任務放入背景線程或者異步執行。但是随着手淘業務發展和人員變更,還是出現了在主線程中執行很重的初始化任務。為此,在ios實作了一套應用運作時方法耗時檢測機制,能夠對應用中所有類的方法調用做耗時統計。友善的找到逾時的方法調用之後,就可以有針對性的做出修改,或删除或異步化。這種方法調用耗時檢測機制同樣适用于app運作過程中,進而找到導緻應用卡頓的根本原因,最後做出對應修改。

分析各個子產品的線程數量,檢查線程池的合理性。通過去掉不必要的線程和線程池,再控制線程池的并發數和優先級。進一步通過架構層的線程池來接管業務方的線程使用,以減少線程太多的問題。

從自身業務出發,去除若幹初始化階段不必要的檔案操作,以及将若幹非實時性要求的檔案操作延後處理。android上對于頻繁讀寫資料庫和sharedpreference以及檔案的子產品,通過增加緩存和降低采樣率等手段減少對io的讀寫。對于sharedpreference進行了專門的優化,減少單個檔案的大小,将毫無聯系的存儲鍵值分開到不同檔案中,并且防止将大資料塊存儲到sharedpreference中,這樣既不利于性能也不利于記憶體,因為sharedpreference會有額外的一份緩存長期存在。

例如搖一搖功能,測試發現應用場景不頻密,但業務使用了高頻率的遊戲模式,會耗電及占用主線程時間。對該功能做了降級處理,降低檢測頻率。同理,對于其他非必須使用但又占據較多資源的子產品也都做了适當的降級處理。

在安卓手機上我們把啟動分為兩類進行檢測和優化:冷啟動和熱啟動。冷啟動是程式程序不存在的情況下啟動,熱啟動是指使用者将程式切換到背景或者不斷按back鍵退出程式,實際程序還存在的情況下點選圖示運作。

之前安卓手淘在按back鍵退出時整個首頁activity銷毀了,熱啟動會經過一個比較長的過程。優化後首頁在退出的時候并不銷毀activity,但是會釋放圖檔等主要資源,在下次熱啟動時就能更快的進入。另外,将手淘歡迎頁的界面從其它bundle轉移到首頁的子產品,在進入歡迎頁時就開始初始化首頁資源,做到更快展示。

在經過一系列的優化後,啟動方面已經有了明顯的改善,在進入首頁的時候不會卡頓,gc次數也減少了一半以上。

二、各個界面的優化

各界面優化我們也是圍繞着提高幀率和加快展現而展開的,手淘的幾個主鍊路界面,都是相對比較複雜的,既使用多圖,也使用了動态模闆的技術。功能越複雜,也越容易産生性能問題,是以常遇到布局複雜、過渡繪制多、activity主要函數耗時、内容展示慢、界面重新布局(layout)、gc次數多等問題。

通過開發者選項的gpu過渡繪制選項檢查界面的過渡繪制情況。該優化并不複雜,通過去掉層疊布局中多餘的背景設定、圖檔控件有前景内容的時候不顯示背景、界面背景定義到activity的主題中、減少drawable的複雜shape使用等手段就可以基本消除過渡繪制,減少對gpu和cpu的浪費。

層級越多,測量和布局的時間就會相應增加,建立硬體清單的時間也會相應增加。有時我們會嵌套很多布局來實作原本隻要簡單布局就可以實作的功能,有時還會添加一些測試階段才會使用的布局。通過删除無用的層級,使用merge标簽或者viewstub标簽來優化整個布局性能。比如一些顯示錯誤界面、加載提示框界面等,不是必須顯示的這些布局可以使用viewstub标簽來提升性能。

另外要靈活使用布局,并不是層級越多就會性能越差,有時候1層的relativelayout會比3層嵌套的linearlayout實作的性能更糟糕。

除了靈活使用布局,另外我們還通過提前inflate以及線上程中做一些必要的inflate等來提前初始化布局,減少實際顯示時候的耗時。對于一些複雜的布局,我們還會自己做複用池,減少inflate帶來的性能損耗,特别是在清單中。

可以通過traceview工具找出主線程的耗時操作和其他耗時的線程并作優化。另外減少主線程的gc停頓,因為即使并行gc,也會對heap加鎖,如果主線程請求配置設定記憶體的話,也會被挂起,是以盡量避免在主線程配置設定較多對象和較大的對象,特别是在ondraw等函數中,以減少被挂起的時間。另外可以通過去掉listview ,scrollview等控件的edgeeffect效果,來減少記憶體配置設定和加快控件的建立時間。

利用本地緩存,主要界面緩存上次的資料,并且配合增量的更新和删除,可以做到資料和服務端同步,這樣可以直接展示本地資料,不用等到網絡傳回資料。

減少不必要的資料協定字段,減少名字長度等,并作壓縮。還可以通過分頁加載資料來加快傳輸解析時間。因為json越大,傳輸和解析時間也會越久,引發的記憶體對象配置設定也會越多。

注意線程的優先級,對于占用cpu較多時間的函數,也要判斷線程的優先級。

通過traceview工具發現,一些banner輪播廣告和文字動畫在移出可視區域後,仍然存在定時重新整理,不僅耗電也影響幀率。優化措施是在移出可視區域後停止動畫輪播。

在listview滑動,廣告動畫變化等過程中,圖檔和文字有變化,經常會發現整個界面被重新布局,影響了性能。尤其布局複雜時,測量過程很費時導緻明顯示卡頓。對于大小基本固定的控件和布局例如textview,imageview來說,這是多餘的損耗。我們可以用自定義控件來阻斷,重寫方法requestlayout、onsizechanged,如果大小沒有變化就阻斷這次請求。對于viewpager等廣告條,可以設定緩存子view的數量為廣告的數量。

中間件的代碼被上層業務方調用的比較頻繁,容易有較多的高頻率函數,也容易産生細節上的問題。除了頻繁配置設定對象外,例如類初始化性能,同步鎖的額外開銷,接口的調用時間,枚舉的使用等等都是不能忽視的問題。

安卓上的gc會引起性能卡頓,必須重點優化。除了第三章會詳細介紹對于圖檔記憶體引起gc的優化,我們還做了如下工作:

減少對象配置設定,找出不必要的對象配置設定,如可以使用非包裝類型的時候,使用了包裝類型;字元串的+号和擴容;handler.post(runnable r)等頻繁使用。

對象的複用,對于頻繁配置設定的對象需要使用複用池。

盡早釋放無用對象的引用,特别是大對象和集合對象,通過置為null,及時回收。

防止洩露,除了最基本的檔案、流、資料庫、網絡通路等都要記得關閉以及unregister自己注冊的一些事件外,還要盡量少的使用靜态變量和單例。

控制finalize方法的使用,在高頻率函數中使用重寫了finalize的類,會加重gc負擔,使得性能上有幾倍的差别。

合理選擇容器,在性能上優先考慮數組,即使我們現在習慣了使用容器,也要注意頻繁使用容器在性能上的隐患點:首先是擴容開銷, hashmap擴容時重新hash的開銷較大。其次是記憶體開銷,hashmap需要額外的map.entry對象配置設定 ,需要額外記憶體,也容易産生更多的記憶體碎片。sparsearray和arraylist等在記憶體方面更有優勢。再次是周遊,對于實作了randomaccess接口的容器如arrylist的周遊,不應該使用foreach循環。

用工具監控和精雕細琢:在頁面滑動過程中,通過memory monitor檢視記憶體波動和gc情況,還可通過allocation tracker工具觀察記憶體的配置設定,發現很多小對象的配置設定問題。

利用trace for opengl工具找出界面上導緻硬體加速耗時的點,例如一些圓角圖檔的處理等。

通過多種工具和手段配合,手淘各個界面性能上有了較大的提高,平均幀率提高了20%,那麼記憶體節省50%又是如何實作的哩,請看下文。

第三章 android手機記憶體節省50%

android上應用出現卡頓的核心原因之一是主線程完成繪制的周期過長引起丢幀。而影響主線程完成繪制時間的主要有兩方面,一方面是主線程處于運作狀态時需要做的任務太多但cpu資源有限,另外一方面是gc時suspend時直接挂起了所有線程包括主線程。gc對總體性能的影響在4.x的系統上尤為突出,一部分是單次gc pause總時長,一部分是使用者操作過程中gc發生的次數。而決定這兩部分的因素就是dalvik記憶體配置設定。那麼在手淘這樣的大型應用中到底是誰占用了記憶體大頭呢?

誰占用了記憶體

基于雙11前的手淘android版本,我們在魅藍note1(4.4 os)上滑動完首頁後,dump出其dalvik heap,整體記憶體占用的分布情況如下圖。可以看出,byte數組(a)占用空間最大,絕大多數是用來存放bitmap的像素資料(pixel data)。另外(c)與(d)一起占用了18.4%, byte數組加上bitmap、bitmapdrawable總共占用了64.4%,成為記憶體占用的主體。

這也從側面說明了手淘是以圖檔為浏覽主體内容的大型應用。而往往圖檔需要較大的記憶體塊,在配置設定時引起gc的可能性也往往最大。那我們能不能将圖檔這部分需要的記憶體移走而不在dalvik heap配置設定呢?如果能,那麼不單gc會明顯減少,同時dalvik heap總大小也會下降50%左右,對整體性能會有顯著的提升。

手淘雙十一系列(一) | 521 性能優化項目揭秘

何處安放的pixel data

ashmem即匿名共享記憶體,使用的核心過程是建立一個/dev/ashmem裝置檔案,控制反轉設定檔案的名字和大小,最終把裝置符交給mmap就得到了共享記憶體。在android系統中binder程序間通信的實作就是依賴ashmem完成不同程序間的記憶體共享。但此處并不利用其共享特性,而是使用它在native heap完成記憶體配置設定。

圖檔空間如何才能使用ashmem,答案在facebook推出的fresco中已有提及,那就是解碼時的purgeable标記,這樣在系統底層解碼位圖時會走ashmem空間配置設定,而非dalvik heap空間。這樣就解決了像素資料存放由dalvik到native的問題了嗎?

bitmapfactory.options options = new bitmapfactory.options();

/*

* inpurgeable can help avoid big dalvik heap allocations (from api level 11 onward)

*/

options.inpurgeable = true;

bitmap bitmap = bitmapfactory.decodebytearray(inputbytearray, 0, inputlength, options);

小心bitmap空包彈

事實并非那麼簡單,最後實際解出來bitmap沒有像素資料(沒有到ashmem配置設定任何空間),根本沒有去完成jpeg或者png解碼。此時的bitmap是個空包彈!它所做的隻是把輸入的解碼前資料拷貝到了native記憶體,如果把這個bitmap交給imageview渲染就糟了,在view.draw()時bitmap會在主線程進行圖檔解碼。

而且不要天真的以為bitmap解碼一次之後再多次使用都不會引起二次解碼,在系統記憶體緊張時底層可能回收ashmem裡這部分記憶體。回收後該bitmap再次渲染時又将在主線程完成一次解碼。如果就這樣直接使用該機制,性能上無疑雪上加霜。

那麼怎樣才能避免這個隐形炸彈呢?還好sdk預留了一個c層方法androidbitmap_lockpixels。而lockpixels底層完成的工作大緻如下圖所示。第一步是preparebitmap完成真正的資料解碼,在工作線程調用androidbitmap_lockpixels避免了在主線程進行資料解碼;第二步是完成對配置設定出來的ashmem空間的鎖定,這樣即使在系統記憶體緊張時,也不會回收bitmap像素資料,避免多次解碼。

手淘雙十一系列(一) | 521 性能優化項目揭秘

貌似解決了bitmap渲染的所有問題,但在手淘中則不然。為了相容低版本系統以及提升webp解碼性能,我們使用了自己的解碼庫libwebp.so,怎樣把它解碼出來的資料也存放到ashmem呢?

libwebp借雞生蛋 

如果自有解碼庫libwebp.so要解碼到ashmem,通過skbitmap、ashmem_create_region實作一套類似的機制是不太現實的。一方面skia庫的源碼編譯相容會存在很大問題,另一方面很多系統層面的核心接口并沒有對外。是以實作這點的關鍵還是要借助系統已經提供的purgeable到ashmem的機制,借雞生蛋,穩定性和成本上都能得到保證:

依據圖檔寬高生成空jpeg。

走系統解碼接口完成ashmem bitmap生成。

覆寫pixel data位址在libwebp完成解碼。

更進一步,遷移解碼前資料

上面談到的記憶體遷移都是針對decoded像素資料的,而encoded圖像資料在解碼時會在dalvik heap儲存一份,解碼完成後再釋放;ashmem方式解碼時在底層又會拷貝一份到native記憶體,這份資料直到整個bitmap回收時才釋放。那能否直接将網絡下載下傳的encoded資料存放到native記憶體,省去dalvik heap上的開銷以及解碼時的記憶體拷貝呢?

的确可以,将網絡流資料直接轉移到memoryfile可實作,但遺憾的是真機測試中發現,小米及其他國産“神機”(自改rom),多線程使用memoryfile擷取fd到bitmapfactory解碼,會出現系統當機,懷疑是在并發情況下系統代碼級别的死鎖造成。手機淘寶放棄了這種方案,改用bytearraypool複用池技術來減少dalvik heap針對encoded image的記憶體配置設定,效果也不錯。如果應用能接受單線程解碼,還是memoryfile方案更具優勢。

是放手的時候了

上文提到bitmap像素資料存放到ashmem,有讀者可能擔心資料回收問題,其實還是由gc來觸發ashmem記憶體的回收。在dalvik層如果一個bitmap已經不被任何地方引用,那麼在下一次gc時該bitmap就會從ashmem中回收,大緻流程示意如下圖。

手淘雙十一系列(一) | 521 性能優化項目揭秘

再看記憶體占用

我們再次在魅藍note1中dump出首頁滑動後的記憶體,如下圖可以看出,原來byte數組(k)大量占用已經不存在了,bitmap(c)與bitmapdrawable(已不在前14名當中)的占用也急驟下降。應用的總體記憶體下降近60%。

手淘雙十一系列(一) | 521 性能優化項目揭秘

在雙11版本上,針對一些熱門機型在搜尋結果頁不斷滾動使用,進行了不同版本的記憶體占用對比分析,如下圖。可以看出,除華為3c和vivo這類系統記憶體偏小使用上一直受到控制、記憶體較為緊張的外,大部分機型記憶體的下降幅度都達到45%以上。

手淘雙十一系列(一) | 521 性能優化項目揭秘

撓走gc之癢

記憶體下降不是最終目的,最終要将gc對性能的影響降到最低。仍然以魅藍note1打開首頁後滑動到底的記憶體堆積圖來做對比。可以看到舊版本記憶體占用上升趨勢相當明顯,一路帶有各式“毛刺”直奔70mb,每形成一個毛刺就意味一次gc。而雙11版本中,記憶體隻在初期有上升,而後很快下降到21mb左右,後期也顯得平滑得多,沒有那麼多的“毛刺”,就意味着gc發生的次數在明顯減少。

手淘雙十一系列(一) | 521 性能優化項目揭秘

舊版本

手淘雙十一系列(一) | 521 性能優化項目揭秘

雙11版本

同時使用一些熱門機型,針對雙十一版本在首頁不斷滑動,進行前後版本的gc_for_alloc次數對比。熱門機型gc次數下降了4~8倍,效果非常明顯。

手淘雙十一系列(一) | 521 性能優化項目揭秘

通過上文描述的各個優化方案,手機淘寶于雙十一前在大部分機型上達到了521目标-android手機記憶體節省50%,啟動時間和頁面幀率提升20%,h5頁面實作1s法則。

從持續不斷的優化中,我們也得到了一套優化的經驗閉環,由觀察問題現象到分析原因,建立監控,定下量化目标,執行優化方案,驗證結果資料再回到觀察新問題。每一次閉環隻能解決部分問題,隻有不斷抓住細微的優化點“啃”下去,才能得到螺旋上升的良好結果。

當然,随着手機機型的日益碎片化,程式功能的複雜化多樣化,性能調優是沒有止境的,在部分低端機和低記憶體手機上手淘性能問題依然不容樂觀。欲窮千裡目,還需更上一層樓,接下來我們還會努力通過更多更細緻的優化方案來達到“如絲般順滑”。

手淘雙十一系列(一) | 521 性能優化項目揭秘