Bugly 技術乾貨系列內容主要涉及移動開發方向,是由 Bugly 邀請騰訊內部各位技術大咖,經過平常工做經驗的總結以及感悟撰寫而成,內容均屬原創,轉載請標明出處。java
在業內,Android 手機一直有着「越用越慢」的口碑。根據第三方的調研數據顯示,有77%的 Android 手機用戶認可本身曾遭遇過手機變慢的影響。他們不明白爲何購買之初「如絲般順滑」的 Android 手機,在使用不到一年以後都會「卡頓」得讓人抓狂!根據咱們初步的測試數據,手機長期所使用產生的磁盤碎片可使得磁盤的寫入效率降低爲原來的50%。是否是有一種「嚇死本寶寶了」的感受。緩存
那麼怎麼辦呢?筆者曾經對這一問題進行分析,且讓我一一貫你道來。網絡
故事的原由是,針對「Android 系統越用越卡的問題」,騰訊某產品團隊但願在自身產品中進行優化,從而提高產品口碑。架構
通過簡單的分析討論,你們認爲形成這種現象多是因爲兩個方面緣由:內存、磁盤:工具
先說內存。爲了保證應用能夠快速被再次調起,Android 在內存管理上採用以下策略:進程保持在內存中,在佔用內存未超過閾值以前不會系統進行主動清理。但隨着應用的增多,試圖保持在內存中的進程將會增多,所以影響系統的流暢度。能夠說,內存與系統卡頓的關係早已經是業界的共識,其解決方案也比較明瞭,即賦予系統主動清理內存的能力,例如待機後殺掉沒必要要的進程。性能
再聊磁盤。長期使用 Android 手機必將產生大量的磁盤碎片,而磁盤碎片將會下降磁盤的讀寫性能,從而影響系統流暢度。可是磁盤碎片是否能對磁盤的讀寫性能形成了很大影響,以致於影響系統流暢度還沒有可知,且暫時也沒有發現能夠進行嘗試的潛在優化點。學習
因而,產品團隊找到了咱們專項測試組,但願分析 Android 越用越卡與磁盤是否有關係,並初步探索系統在磁盤管理模式方面是否存在潛在優化點。這就有了下文。測試
其實,平常的生活經驗(例如 SSD 可讓老筆記本煥發新生)已經咱們可以感受獲得磁盤對系統流暢度的影響很大。可是,這裏仍是有再必要簡單說一下,磁盤是如何影響系統流暢度的。優化
開發過 Android 項目的同窗都知道 Android 在使用網絡的最佳實踐是使用3級緩存的設計來提高系統的流暢度並節省流量:CPU 首先嚐試從內存中加載圖片,若此時圖片存在在內存中則加載成功,不然內存會從磁盤中加載圖片,若此時圖片存在在磁盤中則加載成功,不然磁盤會最終向網絡中下載圖片。spa
其實上述的執行邏輯,也就解釋了磁盤是如何影響系統流暢度的:對於系統流暢度(其實也是各個應用的流暢度)影響最直接的就是 CPU 的執行效率,可是若是這個過程當中內存、磁盤以及網絡的讀寫速度若是跟不上 CPU 的執行效率的話,就會形成 CPU 在處理任務的時候須要花費時間等待數據,從而影響了流暢度。
因此第一個問題就弄清楚了:磁盤的讀寫速度的下降會使得系統流暢度變差!那麼,咱們要分析的問題就轉化成:磁盤在長期使用的過程當中,其讀寫速度會不會下降。
爲了分析清楚磁盤「磁盤在長期使用的過程當中,其讀寫速度會不會下降」這個問題,咱們有必要先弄明白 Android 磁盤所採用的讀寫機制。
經過資料查閱,咱們瞭解到目前,Android 手機大多采用 NAND Flash 架構的閃存卡來存儲內容。NAND Flash 的內部存儲單位從小到大依次爲:Page、Block、Plane、Die,而一個 Device 上能夠封裝若干個 Die。下圖就是一個 NAND Flash 組成結構的示意圖。
爲了方便理解,針對一個 Die,咱們再抽象一下,Page、Block、Plane、Die 的關係以下圖所示。
雖然 NAND Flash 的優勢多多,可是爲了延長驅動器的壽命,它的讀寫操做均是以 Page 爲單位進行的,但擦除操做倒是按 Block 爲單位進行的。
因爲有大量的讀寫操做,因而咱們的 NAND Flash 制定了以下的讀寫規則:
刪除數據時,芯片將標記這些 Page 爲閒置狀態,但並不會立馬執行擦除操做。
寫入數據時,若是目前磁盤剩餘空間充足,則由芯片指定 Block 後直接按 Page 爲單位進行寫入便可。
寫入數據時,若是目前磁盤剩餘空間不足,爲了得到足夠的空間,磁盤先將某塊 Block 的內容讀至緩存,而後再在該 Block 上進行擦除操做,最後將新內容與原先內容一塊兒寫入至該 Block。
那麼問題來了!假如如今我要向磁盤中寫入一張圖片的數據,這個圖片的數據大小恰好爲一個 Page。最壞的狀況就是,內存中剛好只有一個 Block 剛好有一個 Page 的無效數據能夠擦除。爲了存下這張圖片,因而主控就把這個 Block 的全部數據讀至緩存,擦除Block上的內容,再向緩存中加上這個4KB 新數據後最後寫回 Block 中。
個人天啊,其實想存儲的就是1個 Page 的圖片內容,可是實際上確形成了整個 Block 的內容都被從新寫入,同時本來簡單一步搞定的事情被還被分紅了先後四步執行(閃存讀取、緩存改、閃存擦除、閃存寫入)形成延遲大大增長,速度變慢。這就是傳說中的「寫入放大」(Write Amplification)問題。而「寫入放大」也說明了磁盤在長期使用的過程當中,其讀寫速度(尤爲是寫入速度)會存在下降的現象。
不過,既然「寫入放大」(Write Amplification)都這麼出名了,確定不會沒有現成的解決方案的!這個很簡單,Google 一下,咱們就知道解決方案就是 TRIM 技術。
TRIM 是一條 ATA 指令,由操做系統發送給閃存主控制器,告訴它哪些數據佔的地址是「無效」的。在 TRIM 的幫助下,閃存主控制器就能夠提早知道哪些 Page 是「無效」的,即可以在適當的時機作出優化,從而改善性能。這裏要強調下,TRIM 只是條指令,讓操做系統告訴閃存主控制器這個 Page 已經「無效」就算完了,並無任何其它多餘的操做。在測試的過程當中,咱們發現 TRIM 的觸發須要操做系統、驅動程序以及閃存主控三者都支持才能真正意義上實現。例如:
操做系統不支持的狀況:Android 4.3如下均不支持
閃存主控不支持的狀況:Samsung Galaxy Nexus(I9250)所選用的閃存不支持
基於 TRIM 技術,目前常見有兩種方案能夠解決「寫入放大」的問題:
discard 選項。該方案將在掛載 ext4 分區時加上 discard 選項,此後操做系統在執行每個磁盤操做時同時都會執行 TRIM 指令。該方案的優勢是整體耗時短,但影響會到刪除文件時的性能。
fstrim 命令。該方案將選擇合適的時機對整個分區執行 TRIM 操做。相對於方案一,該方案整體耗時較長,但不會影響正常操做時的磁盤性能。
不得不說,若是從用戶的角度出發,仍是 FSTRIM 的方法更靠譜一些,但如何尋找合適的 TRIM 時機就是一個比較講究的問題了。
根據前面的分析,咱們不難理解在 Android 中的 TRIM 選擇經過 fstrim 命令的方式進行實現。那麼,Google 又是如何設計觸發TRIM的時機呢?
經過走讀 Android 源碼(AOSP 4.4.4),能夠了解到 Android 經過系統服務 IdleMaintenanceService 來進行系統狀態監控並決定什麼時候觸發 TRIM。根據 IdleMaintenanceService.java 源碼,咱們繪製了 fstrim 的觸發示意圖以下:
註釋:
有/無操做:距屏幕熄滅||屏保啓動已超過71分鐘
是/否電量充足:維護期20%,非維護期(充電狀態30%,非充電狀態80%)
是/否維護超時:啓動維護已超過71分鐘
是/否已到維護期:據上次啓動維護超過1天
瞭解了這麼多技術背景,那咱們經過測試數據分析閃存碎片和 TRIM 對磁盤 I/O 性能的影響。根據測試目的,具體的測試設置以下:
測試目的:
評估閃存碎片和TRIM對磁盤 I/O 性能的影響
測試方案:
測試對象:LG Nexus 5 with cm-11-20140805-SNAPSHOT-M9-hammerhead
測試步驟:
從新刷機,使用 Bonnie++ 測試 SD 卡目錄的 I/O 性能;
模擬長期使用 SD 卡的過程(期間須要避免TRIM觸發),使用 Bonnie++ 測試 SD 卡目錄的 I/O 性能;
主動觸發 TRIM,使用 Bonnie++ 測試 SD 卡目錄的 I/O 性能。
備註:
模擬長期使用 SD 卡的過程的方法:開發專用的測試應用,該應用將向 SD 卡目錄不停寫入大小隨機的文件,當 SD 卡剩餘空間不足時將刪除所寫入的文件,而後繼續上述操做直到應用退出。
避免 TRIM 觸發的方法:根據 Android 的觸發過程分析,只需設置屏幕常亮並便可避免 TRIM 的觸發。
測試數據:
數據解讀:
經過反覆擦寫 SD 卡,能夠發現 SD 卡的 I/O 效率指標均存在必定幅度的下滑,其中反映磁盤空間分配性能及文件數據寫回性能的指標下滑明顯;
Sequential Output-Block 能夠反映分配磁盤文件空間的效率,經反覆擦寫 SD 卡後,該效率下降至原始值的15-20%,應該是大量的磁盤閒置數據塊形成的影響;
Sequential Output-Rewrite 能夠反映文件系統緩存和數據傳輸的速度,經反覆擦寫 SD 卡製造閒置數據塊後,該效率下降至原始值的50%。
主動調用 TRIM 後,能夠發現 SD 卡的 I/O 效率指標均恢復至接近原始值水平(但仍未徹底達到初始狀態的水平)。
測試結論:
在 TRIM 無效的狀況下,長期使用 SD 卡,磁盤寫入速度會受到明顯影響;
TRIM 對因閒置數據塊形成的 I/O 性能降低有必定的恢復做用;
大量的讀寫操做對 SD 卡形成了必定量的不可恢復的損耗。
完成了上面的工做,不禁得讓咱們大吃一鯨:原來 TRIM 對 SD 卡的讀寫速度的維護如此重要!前面也說到,Android 選擇 FSTRIM 方案的來實現 TRIM,那麼 Android 所設計的 FSTRIM 觸發時機有沒有什麼問題呢?
根據 Android 系統的設定,FSTRIM 預期是每隔24小時觸發一次。因此,接下來咱們須要評估一下,FSTRIM 可否依據上述設定成功被系統觸發。
測試目的:
分析 FSTRIM 可否被按時被系統觸發
測試方案:
測試對象:2臺 Samsung Galaxy Nexus 及2臺 LG Nexus 5
測試步驟:
刷機後,安裝經常使用應用並啓動(均無SIM卡,其中1臺設備開啓 Wifi,另1臺設備關閉 Wifi);
進行 Log 記錄;
強制執行一次 FSTRIM;
滅屏等待30小時左右,提取 Log 記錄進行分析。
測試數據:
開啓WiFi | 關閉WiFi | |
---|---|---|
Samsung Galaxy Nexus | 啓動FSTRIM 1次 | 啓動FSTRIM 1次 |
LG Nexus 5 | 未啓動FSTRIM | 啓動FSTRIM 1次 |
數據解讀:
FSTRIM 大多數狀況會被自動觸發,但也存在沒法觸發的狀況;
根據 FSTRIM 的觸發邏輯,是否開啓 WIFI 對 FSTRIM 的影響主要是有無推送消息(影響滅屏條件)以及不一樣的耗電。
測試結論:
測試數據顯示 FSTRIM 大多數狀況會被自動觸發,但也存在沒法觸發的狀況。可能的緣由是:FSTRIM 對電量的要求略高,因此一旦發生意外狀況(如應用的 PUSH 消息)終止了計劃 FSTRIM 的執行以後,很長時間以內都沒法再知足 FSTRIM 的啓動條件。
因此,如需提升其觸發頻率,咱們能夠考慮下降觸發條件中對電量的要求。
根據前面的分析,咱們能夠從 Android 源碼及測試數據對前面兩個問題作出回答:
磁盤碎片(更準確的說法是 SD 卡中的閒置數據塊)會嚴重影響磁盤的讀寫性能,可能會致使 Android 系統越用越卡,而 Android 系統的 FSTRIM 對此有恢復的做用;
通過實驗分析, FSTRIM 並不必定可以定期(天天一次)執行。而致使這一問題的緣由多是 IdleMaintenanceService 對電量的要求太高(未充電狀態下大於80%)。
固然,咱們能夠經過一下手段對這一問題作出優化嘗試:
FSTRIM 對電量的要求略高,如需提升其觸發頻率能夠從下降觸發條件中對電量的要求;
在必要的狀況下,能夠發送特定的 Intent 事件,使系統強制觸發 FSTRIM。
至此,咱們也大體解答了項目組提出的問題,這個故事也基本能夠告一段落了。
回顧整個分析問題的過程,我發現,做爲一名專項測試人員,儘管我並不須要實際編寫項目中的任何一句代碼,但這並不意味着我不須要了解 Android 及其 Framework 的代碼。實際上,只有在平時的學習和工做中瞭解其工做機制的基礎上,咱們才能設計出合理的測試方案,從而更好 的完成工做。
騰訊 Bugly 是一款專爲移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的狀況以及解決方案。智能合併功能幫助開發同窗把天天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同窗定位到出問題的代碼行,實時上報能夠在發佈後快速的瞭解應用的質量狀況,適配最新的 iOS, Android 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧…