努比亞技術團隊原創(chuàng)內(nèi)容茫藏,轉載請務必注明出處盗冷。
本文是Android
卡頓問題分析三部曲的最后一篇虐译。在前面兩篇文章的理論和工具的分析基礎上鹤耍,本文將結合典型實戰(zhàn)案例夸溶,分析常見的造成卡頓等性能問題的原因夕晓。從系統(tǒng)工程師的總體角度來看 矾策,造成卡頓等性能問題的原因總體上大致分為三個大類:一類是流程執(zhí)行異常磷账;二是系統(tǒng)負載異常;三是編譯問題引起贾虽。
1 流程執(zhí)行異常
在前文Android
卡頓掉幀問題分析之原理篇的內(nèi)容中逃糟,我們以滑動場景為例完整的分析了Android
應用上幀顯示的全流程(默認開啟硬件繪制加速條件下)。下面我們用一張圖來看看這個流程上可能引起卡頓蓬豁、反應延遲等性能問題的點绰咽。
從上圖可以看出,在
Android
應用上幀顯示的各個流程上都可能出現(xiàn)問題導致出現(xiàn)卡頓地粪、反應延遲等性能問題取募。下面我們分類來分析一下各個流程上可能的引起卡頓等性能問題的點。
1.1 system_server引起應用卡頓
一蟆技、理論分析
框架system_server
進程內(nèi)部的各個核心服務AMS
玩敏、WMS
、PKMS
等质礼,都有各自的對象鎖和工作線程聊品,各個服務運行時相互之間存在交互,可能出現(xiàn)某個線程持鎖執(zhí)行耗時操作而其它線程陷入長時間鎖等待的情況几苍。某些情況下當系統(tǒng)框架負載過重或流程出現(xiàn)異常情況下翻屈,其工作線程之間相互等鎖的現(xiàn)象會非常嚴重。如果此時應用App
的UI
線程中有Binder
請求妻坝,而系統(tǒng)框架內(nèi)部處理請求的實現(xiàn)又恰好需要持鎖伸眶,就可能間接導致應用App
的UI
線程阻塞在Binder
請求惊窖,而引起App
應用的卡頓問題。
二厘贼、典型案例分析
問題描述:設備剛開機時界酒,桌面左右滑動出現(xiàn)卡頓。
問題分析:結合Systrace
工具分析問題原因如下:
從以上問題
Systrace
的分析可以看出嘴秸,出現(xiàn)桌面滑動卡頓的原因是:
- 桌面應用的
UI
線程(tid:4927
)中通過Binder
請求訪問框架PKMS
服務的getActivityInfo
接口查詢Activity
的相關信息時出現(xiàn)長時間阻塞毁欣; - 而框架
PKMS
服務中處理此Binder
請求的線程(tid:5271
)邏輯需要持鎖執(zhí)行,而當前此鎖對象的“鎖池”中至少已經(jīng)有7
個線程都已經(jīng)在等待這把鎖岳掐,所以此線程進入鎖對象的“鎖池”中阻塞等待鎖釋凭疮; - 而此時持有該鎖的線程為框架的
android.bg
工作線程(tid:2608
),此線程中正持鎖執(zhí)行IO
寫磁盤等耗時操作串述,且線程的優(yōu)先級較低执解,在剛開機階段系統(tǒng)整體負載比較重的場景下,搶不到CPU
執(zhí)行時間片而長時間處于Runnable
狀態(tài)纲酗,導致運行時間較長衰腌。
三、優(yōu)化思路
針對此類system_server
內(nèi)部的各種鎖等待或流程異常導致的性能問題觅赊。Google
在歷年的Android
版本上也在不斷針對優(yōu)化右蕊,通過優(yōu)化流程盡量的減少流程中不必要的持鎖或減小持鎖范圍。例如在Android P
上推出的LockFreeAnimation
無鎖窗口動畫吮螺,就實現(xiàn)了無需持有WMS
鎖播放窗口動畫尤泽,從而極大減少窗口動畫卡頓的概率;Android 12
上在 PackageManager
中规脸,引入只讀快照減少了 92%
的鎖爭用坯约。另外各大手機廠商也會在自家的ROM
系統(tǒng)上針對這種情況對框架作出持續(xù)的優(yōu)化,通過分析持鎖等待的關系莫鸭,找到問題源頭的當前持鎖線程闹丐,利用一些空間換時間的緩存方案或優(yōu)化CPU
、IO
等資源的調(diào)度與分配被因,減少線程的持鎖的時長卿拴,從而改善其它線程鎖等待的時長。
1.2 Input事件處理引起卡頓
一梨与、理論分析
從前面Android
卡頓掉幀問題分析之原理篇文章中的分析我們知道堕花,屏幕驅(qū)動上報的Input
事件要經(jīng)過框架的InputReader
和InputDispatcher
線程的讀取與分發(fā),然后通過Socket
發(fā)送到應用進程中粥鞋,再經(jīng)過界面View
控件樹的層層分發(fā)后消費處理缘挽。這個過程中任意一個流程出現(xiàn)阻塞就能造成用戶的觸控操作得不到及時的響應,出現(xiàn)用戶感知的系統(tǒng)反應延遲或卡頓現(xiàn)象。
二壕曼、典型案例分析
問題描述:微博應用界面手勢滑動退出時苏研,界面卡住幾秒鐘才有反應。
問題分析:結合Systrace
工具分析問題原因如下:
從以上問題
Systrace
的分析可以看出腮郊,退出應用界面時出現(xiàn)卡住幾秒的原因:
- 首先看
Systrace
上system_server
進程中標識Input
事件分發(fā)的"iq"
和“oq
”隊列的信息摹蘑,可以看到框架InputDispatcher
對事件的分發(fā)處理沒有出現(xiàn)阻塞或漏報等異常情況; - 從微博應用對應的
“wq”
隊列可以看到轧飞,應用進程一直有沒有完成處理消費的Input
事件衅鹿,說明問題在微博應用進程側; - 看
Systrace
上微博應用側的信息过咬,可以看到應用UI
線程很長一段的deliverInputEvent
的tag
(大概持續(xù)2.5
秒左右)大渤,且“aq:ime”
隊列中顯示有持續(xù)很長一段時間的事件處理邏輯。說明應該是在應用進程UI
線程中判斷Input
事件類型為返回鍵的Key
事件援奢,所以先交給輸入法應用進行處理兼犯,但是輸入法進程一直沒有完成處理忍捡,導致應用UI
線程一直處于等待狀態(tài)無法及時處理此Input
事件集漾,說明問題出現(xiàn)在輸入法應用側; - 然后我們用
ps
命令查看當前輸入法進程信息砸脊,發(fā)現(xiàn)輸入法進程(pid:5897
)當前的狀態(tài)為“D”
俊性,說明處于進程“凍結”的休眠狀態(tài)治筒,結合Systrace
上顯示的輸入法進程的主線程信息顯示,發(fā)現(xiàn)其確實是一直處于Sleeping
狀態(tài)。所以此問題出現(xiàn)的原因就是因為輸入法進程被異撤钢“凍結”導致的。和負責維護進程“凍結”功能的同事溝通并分析日志后發(fā)現(xiàn)倘感,問題的原因是用戶安裝并設置了新的輸入法應用留特,進程“凍結”模塊沒有成功識別,將其認定為普通后臺進程所以進行了“凍結”從而導致了異常孩哑。
到此還剩一個疑問就是為什么輸入法進程都被“凍結”而休眠了栓霜,應用UI
線程在等待2.5
秒后還是能繼續(xù)往下處理Input
事件,并成功退出應用Activity
界面呢横蜒?原因就是應用UI
線程進入等待輸入法處理Input
事件時會設置一個超時胳蛮,這個時長就是2.5
秒,如果超時后輸入法進程還是沒有處理完丛晌,就會強行結束等待的邏輯仅炊,由應用UI
線程繼續(xù)往下處理Input
事件。相關源碼如下所示:
/*frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java*/
...
/**
* Timeout in milliseconds for delivering a key to an IME.
*/
static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500;
...
// Must be called on the main looper
int sendInputEventOnMainLooperLocked(PendingEvent p) {
if (mCurChannel != null) {
...
if (mCurSender.sendInputEvent(seq, event)) {
...
Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, seq, 0, p);
msg.setAsynchronous(true);
mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);//1.處理Input事件時設置一個2500ms的超時檢查
return DISPATCH_IN_PROGRESS;
}
...
}
return DISPATCH_NOT_HANDLED;
}
...
case MSG_TIMEOUT_INPUT_EVENT: {
finishedInputEvent(msg.arg1, false, true);// 2.時間到之后如果輸入法還是沒有完成Input事件處理澎蛛,則強行結束輸入法對事件的處理邏輯
return;
}
...
三抚垄、優(yōu)化思路
對用戶觸控事件的響應速度直接關系到用戶對交互設備性能體驗的感知,所以一直以來都是系統(tǒng)性能優(yōu)化工作的重中之重。多年來谷歌督勺,包括SOC
廠商都有針對系統(tǒng)Input
事件的處理流程作出優(yōu)化渠羞,以提升觸控響應速度。例如高通基線上在2018
年左右就有一筆提交智哀,優(yōu)化應用進程側的Input
事件處理流程次询,大概思路就是識別應用UI
線程中收到第一個ACTION_DOWN
的Touch
事件后,調(diào)用sendMessageAtFrontOfQueue
接口在應用UI
線程的消息隊列的最前面插入一幀doFrame
繪制任務瓷叫,這樣界面不用等待下一個Vsync
的信號的到來就能直接上幀顯示屯吊,從而減少整個Input
觸控事件的響應延遲。從Systrace
上表現(xiàn)如下圖所示:
國內(nèi)各大手機廠商也有各自的優(yōu)化方案摹菠。比如硬件上采用觸控采樣率更高的屏幕盒卸,屏幕觸控采樣率達到
240HZ
甚至480HZ
。再比如監(jiān)控到觸控事件后提升CPU
主頻次氨,提升觸控事件處理相關線程和渲染線程的優(yōu)先級等方式蔽介,從而優(yōu)化事件觸控響應速度。而對于應用APP
開發(fā)者來說煮寡,需要做的就是避免在Input
觸控事件的分發(fā)處理流程中執(zhí)行耗時操作虹蓄,以免引起觸控延遲或卡頓等性能問題。
1.3 應用UI線程耗時引起卡頓
一幸撕、理論分析
這部分邏輯主要由應用APP
開發(fā)者控制薇组。根據(jù)前文Android卡頓掉幀問題分析之原理篇中的分析可知,UI
線程耗時過長必然會導致Vsync
周期內(nèi)應用上幀出現(xiàn)超時坐儿。特別是屏幕高刷時代的到來律胀,留給應用UI
線程處理上幀任務的時長越來越短。例如120HZ
的高刷屏幕配上貌矿,一個Vsync
信號周期已經(jīng)縮短到8ms
左右炭菌。這要求應用APP
開發(fā)者能寫出更加高性能質(zhì)量的代碼。應用UI
線程耗時引起的卡頓往往涉及的因素比較多逛漫,下面列舉一些常見原因:
-
UI
線程消息隊列中存在除doFrame
繪制任務外的其它耗時任務黑低,導致Vsync
信號到來后,無法及時觸發(fā)UI
線程執(zhí)行doFrame
繪制上幀任務尽楔,而導致掉幀投储。例如界面布局XML
文件的inflate
解析,如果界面布局文件比較復雜阔馋,就到有大量的IO
與反射等耗時操作玛荞。又或者UI
線程中有decodeBitmap
解析大圖片的耗時操作。 -
UI
幀的doFrame
繪制任務處理耗時過長導致掉幀呕寝。最常見的問題就是應用界面布局存在過度繪制勋眯,導致measure/layout/draw
任務的計算復雜度成倍上升。再比如應用界面布局中的部分View
控件層面如果關閉了硬件繪制加速,就會觸發(fā)View#buildDrawingCache
的耗時操作客蹋,從而導致整個draw
動作耗時過長而引起掉幀塞蹭。 -
UI
線程存在大量的阻塞等待導致上幀超時。UI
線程陷入阻塞等待讶坯,常見的原因就是跨進程的Binder
調(diào)用阻塞和進程內(nèi)的同步鎖競爭等待番电。還有一類情況就是頻繁的GC
內(nèi)存回收引起(一般為進程內(nèi)存抖動或內(nèi)存泄露引起)。
二辆琅、典型案例分析
問題描述:三方應用書旗小說界面上下滑動嚴重掉幀卡頓漱办。
問題分析:結合Systrace
工具分析問題原因如下:
從
Systrace
上可以看到,應用UI
線程上幀的doFrame
流程中主要耗時點在buildDrawingCache
的流程上婉烟。下面我們結合源碼來看看什么情況下draw
流程中會出現(xiàn)buildDrawingCache
的動作:
/*frameworks/base/core/java/android/view/View.java*/
...
public RenderNode updateDisplayListIfDirty() {
...
if (layerType == LAYER_TYPE_SOFTWARE) { //判斷View的layerType為LAYER_TYPE_SOFTWARE娩井,也就是關閉了硬件繪制加速,執(zhí)行buildDrawingCache
buildDrawingCache(true);
...
}else{
...
draw(canvas); //走硬件繪制加速的流程
...
}
....
}
...
從代碼可以看出當判斷某個View
空間的layerType
為LAYER_TYPE_SOFTWARE
似袁,也就是關閉了硬件繪制加速的情況下洞辣,就會觸發(fā)走到buildDrawingCache
的耗時邏輯中。所以此問題就是由于書旗小說應用的界面布局中一個名為OriWebViewEx
的View
控件關閉了硬件繪制加速導致昙衅。
問題描述:Twitter
應用界面上下滑動嚴重掉幀卡頓扬霜。
問題分析:結合Systrace
工具分析問題原因如下:
從
Systrace
上可以看到,三方應用Twitter
出現(xiàn)掉幀的主要原因是:其UI
線程中存在大量耗時的inflate
解析xml
布局文件的操作绒尊,且出現(xiàn)問題時UI
線程已經(jīng)持續(xù)Running
到CPU 5
大核心上但是還是出現(xiàn)嚴重的上幀超時畜挥。
三仔粥、優(yōu)化思路
針對應用UI
線程耗時引起卡頓的問題婴谱,原則就是盡量減輕UI
線程的負擔。針對不同的引起問題原因躯泰,常見的優(yōu)化思路大致如下:
-
異步處理:各種耗時操作盡量放到子線程異步處理谭羔。比如使用
View
的異步線程inflate
方案;decodeBitmap
加載圖片的耗時操作移到子線程統(tǒng)一處理等麦向。 -
邏輯優(yōu)化:必須要在主線程執(zhí)行的邏輯應盡量優(yōu)化瘟裸,減少計算頻次,避免重復計算诵竭。比如采用約束布局解決嵌套過多的問題话告,避免過度繪制;優(yōu)化應用的內(nèi)存占用卵慰,避免內(nèi)存泄露沙郭、內(nèi)存抖動等問題,從而減少
GC
觸發(fā)的次數(shù)裳朋;優(yōu)化內(nèi)部代碼邏輯病线,盡量減少主線程陷入同步鎖競爭等待的狀態(tài);原則上不要主動去關閉硬件繪制加速。 -
流程復用:能復用的邏輯盡量復用送挑,以避免多次調(diào)用產(chǎn)生的性能開銷绑莺。比如
ListView
的Adapter
中實現(xiàn)View
的復用,減少View
的inflate
執(zhí)行次數(shù)惕耕。另外UI
線程中Binder
請求框架查詢一些系統(tǒng)信息纺裁,能夠一次查完就不要分多次執(zhí)行。且查詢結果應盡量緩存在內(nèi)存中實現(xiàn)復用司澎,避免多次反復的查詢造成主線程頻繁陷入Binder
阻塞等待对扶。
1.4 SurfaceFlinger耗時引起卡頓
一、理論分析
從前文的分析可以知道惭缰,SurfaceFlinger
在Android
系統(tǒng)的整個圖形顯示系統(tǒng)中是起到一個承上浪南、啟下的作用,負責收集不同應用進程Surface
的繪制緩存數(shù)據(jù)進行的合成漱受,然后發(fā)送到顯示設備络凿。所以,如果SurfaceFlinger
的處理流程上出現(xiàn)耗時或阻塞昂羡,必然會導致整個應用的上幀顯示的流程出現(xiàn)超時而掉幀絮记。特別是隨著屏幕高刷時代的到來,Vsync
周期不斷縮短虐先,留給SurfaceFlinger
處理的時間也越來越短怨愤,對SurfaceFlinger
的性能提出了更高的要求。這也是谷歌蛹批、SOC
廠商包括各大手機廠商做系統(tǒng)性能優(yōu)化的重點撰洗。
二、典型案例分析
問題描述:設置120HZ
高刷模式下腐芍,手機錄屏差导、投屏場景下出現(xiàn)幀率下降。
問題分析:結合Systrace
工具分析問題原因如下:
從
Systrace
上可以看到猪勇,開啟錄屏或投屏后设褐,部分Layer
需要采用效率更低的GPU
合成方式(主要是因為高通平臺HWC
不支持回寫),SurfaceFlinger
的主線程中需要實現(xiàn)drawLayers
的動作泣刹,還需要將圖層數(shù)據(jù)寫入到MediaCodec
緩存中助析,總體負載較高,導致在8ms
(120HZ
高刷模式下)內(nèi)無法完成一幀數(shù)據(jù)的處理而出現(xiàn)幀率下降的問題椅您。
三外冀、優(yōu)化思路
針對SurfaceFlinger
引起的性能卡頓問題,常見的優(yōu)化思路有:
- 谷歌通過
cpuset
的配置襟沮,默認場景下SurfaceFlinger
只能使用4
個CPU
小核心運行锥惋;部分廠商會在投屏或錄屏等負載比較重的場景下放開這個限制昌腰,讓SurfaceFlinger
進程能跑到CPU
大核心上。 - 部分廠商在開啟錄屏的情況下膀跌,會鎖定屏幕刷新率最高不超過
60HZ
遭商,以減輕SurfaceFlinger
進程的壓力。 -
Android 12
上捅伤,谷歌就將各個應用對應的BufferQueue
的管理工作移動到應用進程側處理劫流,以減輕SurfaceFlinger
進程的負載。
2 系統(tǒng)負載異常
CPU
丛忆、GPU
祠汇、內(nèi)存、磁盤等都是系統(tǒng)內(nèi)各個進程任務的運行所必需的公共系統(tǒng)資源熄诡。這就涉及到一個復雜的資源調(diào)度與分配的問題可很,實現(xiàn)將系統(tǒng)資源與各個不同進程的需求進行合理的匹配,以保證進程任務能夠流暢的運行凰浮。這就涉及到兩個問題:
-
這些系統(tǒng)公共資源是否充足我抠。比如當進程需要時,當前
CPU
核心的運行主頻算力是否足夠高袜茧,系統(tǒng)剩余內(nèi)存是否足夠大菜拓,磁盤的讀寫速度是否足夠快。 -
這些系統(tǒng)資源的分配是否合理笛厦。比如當前優(yōu)先級更高的前臺應用進程相對于后臺進程是否能獲得更多的
CPU
調(diào)度運行時間片和I/O
帶寬等資源纳鼎,以保證界面的流暢運行。
從軟件的角度出發(fā)裳凸,每個應用任務進程都希望自己獲得無限的CPU
算力贱鄙、無限的內(nèi)存資源分配等能力,以保證其能夠流暢的運行登舞。但是從硬件的角度出發(fā)贰逾,各種系統(tǒng)的公共資源配置都是有限的悬荣,不可能無限的提供給每個任務進程菠秒,且在移動設備上還要考量功耗與發(fā)熱的問題,需要限制各種系統(tǒng)資源的供給氯迂。所以如何協(xié)調(diào)這種供需關系之間的矛盾践叠,將寶貴的系統(tǒng)資源合理的分配給當前最需要的任務進程,以最小的資源開銷保證系統(tǒng)的流暢運行嚼蚀,就是考驗操作系統(tǒng)的設計者和性能優(yōu)化工程師的功力的問題禁灼,弄不好就可能出現(xiàn)應用任務進程的運行受限于系統(tǒng)運行資源的分配而出現(xiàn)各種性能問題。
2.1 CPU調(diào)度引起卡頓
一轿曙、理論分析
CPU
算力資源絕對是系統(tǒng)內(nèi)最重要的公共資源之一弄捕,每個應用程序的正常運行都需要CPU
的調(diào)度執(zhí)行僻孝。目前的主流移動設備搭載的CPU
基本上都是采用多核心架構設計,按照算力的大小設置有多顆CPU
運算核心(以高通最新的驍龍8 gen 1
處理器為例守谓,其擁有8
顆CPU
運算核心穿铆,包含四個小核、三個中核和一個大核斋荞,算力依次遞增荞雏,功耗也逐漸增大)。
且
Linux
操作系統(tǒng)內(nèi)核中會有相應的scheduler
和governor
調(diào)度器平酿,來管理每顆CPU
核心上當前運行的任務的排程凤优,以及每顆CPU
核心當前運行的主頻(每顆CPU
核心都有一個運行主頻參數(shù)可以支持在一定的范圍內(nèi)動態(tài)調(diào)節(jié),主頻越高算力越強蜈彼,相應的功耗也就越大)筑辨、節(jié)能狀態(tài)等參數(shù),以達到最佳的性能與功耗的平衡幸逆。但是很多時候由于CPU
運行核心上的任務排布不合理挖垛,或運行主頻過低導致的算力過低,無法滿足應用進程的需求秉颗,就可能導致應用出現(xiàn)卡頓掉幀等性能問題痢毒。常見的一些原因如下:
- 后臺應用進程任務持續(xù)運行搶占
CPU
運行資源,或者處于功耗管控直接關核蚕甥,導致前臺應用的UI
線程任務無法及時的獲得CPU
運行算力執(zhí)行哪替,長時間處于Runnable
狀態(tài),而出現(xiàn)上幀超時掉幀問題菇怀; - 某些時候出于功耗與發(fā)熱的考量凭舶,將
CPU
的運行主頻壓的過低,導致算力不足爱沟。應用的上幀線程上的任務需要Running
很長一段時間帅霜,而出現(xiàn)上幀超時掉幀。
二呼伸、典型案例分析
問題描述:騰訊視頻應用中觀看視頻時身冀,播放彈幕卡頓。
問題分析:結合Systrace
工具分析問題原因如下:
從
Systrace
上可以看到:騰訊視頻播放視頻時出現(xiàn)掉幀卡頓的原因括享,是因為其負責彈幕上幀的線程無法獲得CPU
運行資源搂根,而長時間處于Runnable
狀態(tài),導致上幀出現(xiàn)嚴重超時铃辖。進一步分析原因發(fā)現(xiàn)存在兩個問題:一個就是看
CPU Trace
信息發(fā)現(xiàn)此時只有CPU 0-3
四個小核心和一個CPU 4
一個中核心在運行剩愧,而算力更強的CPU 5-7
都處于關閉狀態(tài),導致系統(tǒng)總體算力受限娇斩;另一個問題就是前臺應用騰訊視頻進程內(nèi)的所有線程運行任務都只分配到CPU 4
上運行仁卷,其它幾個小核心處于空閑狀態(tài)也沒有使用穴翩,導致騰訊視頻應用的運行嚴重受限。最終經(jīng)過分析找到問題的根本原因就是:一锦积、關閉算力更強的
CPU 5-7
的原因藏否,是系統(tǒng)組為了降低用戶全屏看視頻場景下的功耗而故意為之;二充包、騰訊視頻只能運行在CPU 4
上的原因副签,是因為該項目使用的是高通最新的SM8450
平臺,該平臺采用ARM V9
架構設計基矮,32
位的應用只能使用CPU4-6
三個中核運行淆储,而應用商店提供的騰訊視頻剛好就是32
位的版本。至此問題水落石出家浇,后面解決問題思路就是:一方面推動應用商店上架騰訊視頻
64
位版本本砰;二是系統(tǒng)組那邊需要針對32
位的應用前臺全屏看視頻的場景下的CPU
配置做出調(diào)整,不再全部關閉CPU 5-7
三個算力較強的核心钢悲。
問題描述:桌面界面左右滑動出現(xiàn)卡頓掉幀点额。
問題分析:結合Systrace
工具分析問題原因如下:
從
Systrace
上可以看到:前臺應用桌面出現(xiàn)掉幀的原因是其UI
線程搶不到CPU
運算資源而長時間處于Runnable
狀態(tài)。而此時莺琳,CPU
上分布有大量后臺應用的任務持續(xù)運行还棱。出現(xiàn)這個問題的原因是國內(nèi)部分應用存在很多進程保活機制惭等,會在后臺持續(xù)運行珍手,并想方設法提升其進程的優(yōu)先級。所以在系統(tǒng)關閉了相關針對性的管控后就會出現(xiàn)問題辞做。
三琳要、優(yōu)化思路
針對CPU
調(diào)度問題引起的性能卡頓問題,常見的優(yōu)化思路有:
-
基于
cgroup
的進程資源隔離秤茅。Android
系統(tǒng)上繼承了linux
的cgroup
機制稚补,以控制進程的資源占用,讓重要的進程獲得更多的系統(tǒng)資源框喳。涉及的系統(tǒng)資源如下圖所示:
cgroup.png
其中cpuset
(可以實現(xiàn)限制進程只能運行在指定的CPU
核心上)课幕、cpuctl
(可以實現(xiàn)控制前后臺進程的CPU
用量)和freezer
(可以實現(xiàn)控制進程處于凍結休眠狀態(tài),完全放棄CPU
執(zhí)行權)就是對進程CPU
資源的控制帖努。后面的top-app
標識當前進程所屬的資源分組撰豺。cgroup
機制中會將不同的運行進程分成不同的資源組,比如前臺應用屬于top-app
組拼余,后臺應用屬于background
組。不同的組可以支持配置不同的資源使用權限(比如通過cpuset
的配置亩歹,background
組中的進程被限制只能運行在cpu 0-3
三個小核心上匙监,而處于top-app
組中的進程可以使用所有的cpu
核心資源)凡橱。以cpuset
為例,如下圖所示:
cgroup_2.png
cgroup_3.png
通過這種限制后臺不重要的進程的CPU
資源占用亭姥,從而讓前臺重要的應用進程獲得更多的CPU
資源(至于進程的前后臺優(yōu)先級定義稼钩,主要是由框架AMS
服務來管理維護,通過寫進程的oom_score_adj
值來標識)达罗。國內(nèi)各大手機廠商的系統(tǒng)性能優(yōu)化的策略中坝撑,有很大一部分工作都是依賴于cgroup
機制來實現(xiàn)的,通過對系統(tǒng)資源精細的定義劃分粮揉,和應用進程優(yōu)先級與功能場景的精準識別巡李,來實現(xiàn)對資源的合理分配,達到性能與功耗的最佳平衡扶认。 基于場景識別的
CPU
調(diào)度策略配置侨拦。主要工作包括:
-
對
CPU
基礎配置影響各核的性能輸出:包括CPU Frequency
運行主頻調(diào)節(jié)、排程器大核小核工作分配策略upmigrate/downmigrate/sched_boost
的調(diào)節(jié)(根據(jù)CPU
各核心的任務負載辐宾,對線程任務進行大小核分布的遷移調(diào)整)狱从,以上都是通過Soc
廠商提供的Perf
服務接口設置參數(shù),去寫相關的設備節(jié)點實現(xiàn)叠纹,以高通平臺為例相關節(jié)點如下圖所示:
cpu節(jié)點配置.png
還有溫控策略的配置調(diào)整(出于功耗與發(fā)熱的考量楼眷,移動設備上溫控機制thermal-engine
服務,會根據(jù)遍布設備內(nèi)部的各個溫度傳感器讀取的溫度值前鹅,按照一定的溫度閾值對CPU
運行主頻進行限制灌危,以防止設備過熱);- 對場景的定義識別與策略配置:比如定義應用冷啟動場景冒窍,在框架進行識別递沪,并動態(tài)將
CPU
主頻進行拉升一段時間,以提升應用打開速度综液;定義視頻播放的場景款慨,在框架進行識別,然后對CPU
主頻進行限頻谬莹,在保證播放流暢的前提下檩奠,盡量降低功耗和發(fā)熱;這塊也屬于國內(nèi)各大手機廠商的系統(tǒng)性能優(yōu)化的核心工作附帽。如何實現(xiàn)精確的場景定義與識別埠戳,精準控制CPU
的性能輸出,用最小的功耗蕉扮,保證應用界面的流暢運行整胃,就是對廠商功力的考驗。
- 對場景的定義識別與策略配置:比如定義應用冷啟動場景冒窍,在框架進行識別递沪,并動態(tài)將
- 對三方應用后臺相互喚醒和進程痹樱活機制的限制屁使,防止其在后臺持續(xù)運行搶占CPU算力資源在岂。這塊也是國內(nèi)各大手機廠商系統(tǒng)優(yōu)化的重點工作之一。
2.2 GPU調(diào)度引起卡頓
一蛮寂、理論分析
GPU
算力資源是繼CPU
算力外蔽午,系統(tǒng)內(nèi)另一個重要的公共資源,從前文Android
應用上幀顯示的流程分析可知酬蹋,應用對每一幀畫面的渲染處理都離不開GPU
算力的支撐及老。和CPU
類似,設備的GPU
也有自己的運行主頻(運行主頻越高范抓,算力越強骄恶,同時發(fā)熱也就越大),且支持在一定的范圍內(nèi)調(diào)節(jié)尉咕,以實現(xiàn)功耗和性能的最佳平衡叠蝇。以高通SM8450
平臺所搭載的型號為Adreno 730
的GPU
為例,其參數(shù)信息如下圖所示:
從上圖可以看到年缎,
GPU
支持在一定的范圍內(nèi)動態(tài)調(diào)節(jié)運行主頻悔捶,Soc
廠商和國內(nèi)的手機廠商會根據(jù)任務負載和使用場景,動態(tài)調(diào)節(jié)GPU
的工作頻率以實現(xiàn)功耗與性能的最佳平衡单芜。但是如果調(diào)節(jié)不合理蜕该,就可能引起性能問題,常見的一些原因如下:
- 部分大型游戲應用(如原神)需要大量的
GPU
算力進行游戲畫面的實時渲染處理洲鸠,如果系統(tǒng)的GPU
運行主頻算力沒有及時的跟上來堂淡,無法滿足應用對GPU
算力的需求就會導致游戲應用上幀時出現(xiàn)阻塞超時而掉幀; - 部分應用在后臺利用
GPU
做一些圖像處理的并行計算扒腕,搶占了系統(tǒng)的GPU
算力資源绢淀;就可能導致前臺應用繪制上幀時,由于GPU
算力不足瘾腰,使其渲染線程陷入長時間阻塞等待GPU
渲染完成的Fence
信號的狀態(tài)皆的,最終出現(xiàn)上幀超時卡頓的問題。
二蹋盆、典型案例分析
問題描述:三方應用UC
瀏覽器界面滑動卡頓费薄。
問題分析:結合Systrace
工具分析問題原因如下:
從上圖的
Systrace
可以看出,UC
瀏覽器出現(xiàn)上幀超時的主要原因是栖雾,其RenderThread
渲染線程長時間阻塞在queueBuffer
上幀的流程上楞抡,而queueBuffer
阻塞在SurfaceFlinger
進程那邊的Binder
響應,而SurfaceFlinger
進程中又需要等待GPU
那邊完成畫面渲染處理的fence
信號的(從應用進程側的waiting for GPU completion
的systrace tag
也能看出)析藕。如下簡化代碼所示:
/*frameworks/native/libs/gui/BufferQueueProducer.cpp*/
status_t BufferQueueProducer::queueBuffer(int slot,
const QueueBufferInput &input, QueueBufferOutput *output) {
ATRACE_CALL();
...
// Wait without lock held
if (connectedApi == NATIVE_WINDOW_API_EGL) {
// Waiting here allows for two full buffers to be queued but not a
// third. In the event that frames take varying time, this makes a
// small trade-off in favor of latency rather than throughput.
lastQueuedFence->waitForever("Throttling EGL Production"); // 等待GPU fence信號
}
return NO_ERROR;
}
也就是說召廷,問題出在系統(tǒng)的GPU
算力資源比較緊張。后來分析應用掉幀的同一時間段系統(tǒng)的其它運行進程的systrace
表現(xiàn),發(fā)現(xiàn)系統(tǒng)的圖庫應用正在執(zhí)行一些Bitmap
圖像處理的動作柱恤。再結合日志分析后與負責圖庫的同事溝通数初,最終找到問題的根本原因所在:圖庫應用中有一個事物分類相冊的功能找爱,當時正在后臺使用GPU
進行照片圖像的特征識別與處理的邏輯梗顺,搶占了系統(tǒng)GPU
算力資源,從而導致前臺UC
瀏覽器應用的渲染線程阻塞在等待GPU
的fence
信號的流程而出現(xiàn)掉幀车摄。
三寺谤、優(yōu)化思路
針對GPU
調(diào)度問題引起的性能卡頓問題,常見的優(yōu)化思路有:
-
基于場景的
GPU
調(diào)度策略配置吮播。這塊主要是結合前臺應用場景的需求变屁,動態(tài)調(diào)節(jié)GPU
的運行主頻等參數(shù),以最小的功耗滿足應用的性能需求意狠。這塊考驗各大手機廠商的調(diào)教水平粟关,也是廠商進行游戲優(yōu)化調(diào)教的重點工作之一。 -
后臺應用進程搶占
GPU
算力資源的管控环戈。主要是限制后臺應用的運行闷板,防止其出現(xiàn)搶占GPU
算力資源的行為,以保證前臺應用的流暢運行院塞。
2.3 低內(nèi)存引起卡頓
一遮晚、理論分析
內(nèi)存也絕對算的上是一個重要的系統(tǒng)公共資源,系統(tǒng)內(nèi)任何進程的正常運行都需要消耗一定的內(nèi)存資源拦止。一般來講設備的RAM
硬件物理內(nèi)存的大小是固定且有限的(4G县遣、8G或12G
等),而系統(tǒng)內(nèi)運行的所有進程都需要共享這些有限內(nèi)存資源汹族。如何用有限的內(nèi)存資源去滿足系統(tǒng)各個應用進程無限的需求萧求,這就涉及到一個系統(tǒng)內(nèi)存管理的邏輯。這是一個復雜的問題顶瞒,幾乎貫穿了整個Android
系統(tǒng)的各個層級夸政。從Linux
內(nèi)核到Art
虛擬機,再到Framework
框架搁拙,甚至到APP
應用秒梳,都會有各自的內(nèi)存管理的策略。關于Android
系統(tǒng)的內(nèi)存管理的詳細邏輯箕速,由于篇幅所限本文就不詳細展開酪碘。我們以systemui
應用進程為例,來看看一個普通應用進程的內(nèi)存占用分布:
合理的內(nèi)存的分配與管理無論對系統(tǒng)和應用來講都至關重要盐茎。因為內(nèi)存問題既能引發(fā)穩(wěn)定性問題兴垦,也能引發(fā)卡頓性能問題。下面我們分別來分析一下:
-
內(nèi)存引起的穩(wěn)定性問題:
- 因為系統(tǒng)總的內(nèi)存資源是有限的,為了防止單個應用進程無限制的占用內(nèi)存資源探越,
Android
系統(tǒng)設計了很多機制來遏制這種情況的產(chǎn)生狡赐。比如在虛擬機層面,谷歌設計對每個應用進程占用的Dalvik Heap
的內(nèi)存大小做出了限制钦幔,超過一定的閾值(記錄在dalvik.vm.heapgrowthlimit
的property
中枕屉,一般為256M
),就會觸發(fā)OutOfMemeory
的異常鲤氢,導致應用進程崩潰搀擂。此外Android
系統(tǒng)還引入了lmkd
低內(nèi)存守護機制,當系統(tǒng)總的可用內(nèi)存低于一定的閾值卷玉,就會按照進程優(yōu)先級哨颂,將占用內(nèi)存比較大的應用進程殺掉。 -
32
位的應用虛擬內(nèi)存的尋址空間最大為4G
相种,當應用進程的虛擬內(nèi)存占用超過4G
時威恼,就無法繼續(xù)為其分配內(nèi)存,產(chǎn)生OOM
類型的報錯寝并,出現(xiàn)應用崩潰或黑屏等問題箫措。 - 另外
Android
系統(tǒng)基于Linux
內(nèi)核,也績承了Linux
系統(tǒng)對應用進程的相關限制食茎。如果應用進程中開啟的線程過多(每創(chuàng)建一個空線程也會有一定的內(nèi)存開銷)蒂破,超過/proc/sys/kernel/threads-max
中定義的上限,也會報出OOM
異常導致應用進程崩潰别渔;再比如應用進程中打開的文件句柄過多附迷,超過/proc/pid/limits
節(jié)點中的定義上限,也會報出OOM
異常導致應用進程崩潰哎媚。如下圖所示:
oom.png
- 因為系統(tǒng)總的內(nèi)存資源是有限的,為了防止單個應用進程無限制的占用內(nèi)存資源探越,
內(nèi)存引起的性能性問題:
當應用出現(xiàn)內(nèi)存抖動或內(nèi)存泄露的問題時喇伯,就會導致虛擬機的
GC
頻繁工作,甚至觸發(fā)stop-the-world
事件拨与,造成應用的UI
線程出現(xiàn)掛起阻塞稻据,最終導致上幀出現(xiàn)超時而掉幀。-
當后臺活躍運行的應用進程過多時买喧,會造成系統(tǒng)總體的剩余內(nèi)存偏低捻悯。此時主要有兩個問題可能會造成應用的卡頓:
a、當內(nèi)存
zone
處于低水位時淤毛,內(nèi)核會頻繁喚醒kswapd
的線程起來執(zhí)行內(nèi)存回收的動作今缚;另外內(nèi)核PSI
內(nèi)存壓力信號也會頻繁喚醒lmkd
進程,進行掃描和殺進程低淡。這些動作都會占用CPU
運行資源姓言,導致前臺應用UI
線程由于搶不到運行CPU
資源而出現(xiàn)長時間處于Runnable
的問題瞬项;b、當系統(tǒng)內(nèi)存不足時何荚,
kswapd
的線程被喚醒進行內(nèi)存回收囱淋,會將page cache
內(nèi)存頁中已經(jīng)緩存的磁盤文件信息進行大量釋放,導致應用進程中的一些IO
讀文件操作(比如view
讀文件餐塘,讀配置文件妥衣、讀odex
文件等),由于page cache
無法命中唠倦,觸發(fā)缺頁中斷称鳞,陷入內(nèi)核態(tài)阻塞等待涮较,一直等到readahead
機制從磁盤中將文件內(nèi)容全部讀取出來才能釋放返回稠鼻。當大量進程在進行IO
操作時,又進一步加重底層IO
的阻塞時長狂票。最終導致前臺應用的UI
線程長時間處于Uninterruptible Sleep - Block I/O
的狀態(tài)而出現(xiàn)上幀超時卡頓問題候齿。
二、典型案例分析
問題描述:在三方應用唯品會的首頁界面上下滑動嚴重掉幀卡頓闺属。
問題分析:結合Systrace
工具分析問題原因如下:
從
Systrace
上可以看到慌盯,唯品會應用出現(xiàn)掉幀卡頓的主要原因如下:
系統(tǒng)內(nèi)存緊張,頻繁喚醒
kswapd0
線程進行內(nèi)存回收操作掂器,其持續(xù)搶占CPU 5
大核心運行亚皂,導致應用的UI
線程和RenderThread
渲染線程頻繁出現(xiàn)搶不到CPU
資源得不到執(zhí)行,而處于Runnable
的狀態(tài)国瓮,從而導致應用上幀超時而掉幀灭必;系統(tǒng)內(nèi)存緊張時,
page cache
磁盤文件緩存被內(nèi)存回收乃摹,導致應用的UI
線程和RenderThread
渲染線程中的一些IO
讀文件操作無法命中page cache
緩存而頻繁陷入內(nèi)核Uninterruptible Sleep - Block I/O
的狀態(tài)禁漓,導致應用上幀超時而掉幀。
三孵睬、優(yōu)化思路
針對內(nèi)存問題引起的性能卡頓問題播歼,常見的優(yōu)化思路有:
-
在應用進程側:作為應用開發(fā)者:
需要解決應用自身的內(nèi)存泄漏問題,避免頻繁的內(nèi)存分配回收出現(xiàn)的內(nèi)存抖動現(xiàn)象掰读;
并盡量優(yōu)化應用的內(nèi)存占用秘狞,包括優(yōu)化圖片加載占用、采用內(nèi)存更高效的數(shù)據(jù)結構(如
ArrayMap
)蹈集、采用對象池和圖片復用等內(nèi)存復用機制烁试、功能比較復雜的應用采用多進程方案、盡量優(yōu)化應用APK
包體積大小雾狈、監(jiān)聽onTrimMemory
事件并及時釋放內(nèi)存中的不必要緩存等廓潜;盡量不要做一些應用進程保活的機制,以免增加系統(tǒng)總體的內(nèi)存負擔辩蛋;
將應用升級適配到64位版本呻畸,以免陷入虛擬內(nèi)存耗盡而進程崩潰的局面。
使用
Android Studio
自帶的Profile
分析工具進行內(nèi)存分析與優(yōu)化悼院,具體用法可以參考這篇文章:http://www.reibang.com/p/72b213fa6a26伤为。 -
在系統(tǒng)總體層面:這部分內(nèi)容主要屬于谷歌和手機廠商的工作,常見的一些針對性優(yōu)化手段有:
調(diào)整
lowmemorykiller
的殺進程的門限值据途,讓其提前介入清理掉一些低優(yōu)先級的進程而釋放內(nèi)存绞愚,以免系統(tǒng)陷入低內(nèi)存狀態(tài);適當提高
extra_free_kbytes
值颖医,讓kswapd
線程能提前介入進行內(nèi)存回收位衩,以免系統(tǒng)陷入低內(nèi)存狀態(tài);框架中設計后臺應用查殺功能熔萧,定期清理后臺低優(yōu)先級應用進程糖驴,并限制三方應用的后臺保活機制佛致,從而釋放內(nèi)存贮缕,以免系統(tǒng)陷入低內(nèi)存狀態(tài);
通過
cpuset
配置限制kswapd
不讓其使用部分CPU
大核俺榆,從而讓出CPU
資源給前臺應用的UI
和渲染線程使用感昼;配置和啟動
ZRAM
和app compaction
后臺內(nèi)存壓縮機制,從而減少應用進程后臺的內(nèi)存占用罐脊,騰出更多的系統(tǒng)可用內(nèi)存定嗓;優(yōu)化系統(tǒng)的磁盤
IO
性能,采用UFS3.1
等性能更強的存儲器爹殊,以減輕系統(tǒng)出現(xiàn)內(nèi)存低的情況時蜕乡,出現(xiàn)IO
阻塞的時長。
2.4 磁盤I/O引起卡頓
一梗夸、理論分析
磁盤是現(xiàn)代計算機系統(tǒng)實現(xiàn)數(shù)據(jù)持久化存儲的核心硬件模塊层玲。也是繼CPU
和內(nèi)存外系統(tǒng)內(nèi)另一個重要的公共資源。Android
系統(tǒng)內(nèi)應用程序的運行一般都會伴隨有大量的磁盤I/O
讀寫的操作反症,比如讀寫數(shù)據(jù)庫辛块、加載XML
布局文件、讀取配置文件等铅碍。但是相對于CPU
和內(nèi)存相比润绵,磁盤的讀寫訪問速度是比較慢的,很容易成為CPU
運行的瓶頸胞谈,導致系統(tǒng)被拖累尘盼。當應用進程的線程中發(fā)生直接I/O
磁盤讀寫時憨愉,就會導致線程進入Uninterrupt Sleep-Block I/O
阻塞狀態(tài),如下圖所示:
在開始分析磁盤
I/O
讀寫的瓶頸引起性能問題原因之前卿捎,我們先借用一張簡化圖來總體看看Linux
系統(tǒng)的I/O
棧:從上圖可以看出配紫,整個
Linux
下的 I/O
棧大致有三個層次:
-
文件系統(tǒng)層,以
write
為例午阵,內(nèi)核拷貝了write
參數(shù)指定的用戶態(tài)數(shù)據(jù)到文件系統(tǒng)Cache
中躺孝,并適時向下層同步。 -
塊層底桂,管理塊設備的
I/O
隊列植袍,對I/O
請求進行合并、排序籽懦。 -
設備層于个,通過
DMA
與內(nèi)存直接交互,完成數(shù)據(jù)和具體存儲設備之間的交互猫十。
可能存在的性能瓶頸問題如下:
- 為了優(yōu)化文件讀寫的性能览濒,減少磁盤的
I/O
操作,Linux
內(nèi)核系統(tǒng)提供了頁緩存(Page Cache
)技術拖云,以頁為單位將磁盤上的文件內(nèi)容緩存在內(nèi)存中,從而提供了接近于內(nèi)存讀寫速度的文件順序讀寫速度应又,也就是Buffered I/O
宙项。但是當系統(tǒng)剩余內(nèi)存不足時,系統(tǒng)會回收Page Cache
頁緩存內(nèi)存株扛,導致文件讀寫操作直接落到磁盤上尤筐,從而極大降低I/O
性能。這也就前一小節(jié)中分析的低內(nèi)存導致的性能卡頓問題洞就。 -
上層所有的
I/O
請求都會放入隊列中進行合并排隊處理盆繁,這樣就可能引發(fā)不同應用進程之間的資源搶占問題。比如后臺進程存在大量讀寫磁盤的操作請求旬蟋,占用系統(tǒng)的I/O
資源油昂,這就會導致前臺應用的I/O
請求出現(xiàn)在隊列中阻塞排隊等待的現(xiàn)象,最終導致掉幀卡頓問題倾贰。 -
文件系統(tǒng)的性能瓶頸冕碟。比如針對
NAND
閃存特性設計的f2fs
文件系統(tǒng),相比于ext4
文件系統(tǒng)匆浙,能夠提升小文件的隨機讀寫性能安寺,并能減少碎片化的問題。再比如Android 11
上引入的Fuse
文件系統(tǒng)首尼,增加I/O
讀寫的開銷挑庶,導致sdcard
路徑下的部分路徑文件的I/O
讀寫性能下降言秸。 -
閃存的讀寫性能瓶頸。一個就是閃存硬件的讀寫性能差異迎捺,比如
UFS 3.0
相對于UFS 2.1
來講順序讀取速度快90%
井仰,順序?qū)懭胨俣雀翘嵘?code>160%;另外一個問題就是閃存重復寫入破加,需要先進行擦除操作俱恶,這樣隨著時間的推移,磁盤碎片變多范舀,剩余空間少合是,一個內(nèi)存頁的寫入會引起整個磁盤塊的遷移,造成耗時操作锭环,也就是早期Android
手機給人形成“越用越卡”的印象的原因之一聪全。
二、優(yōu)化思路
針對I/O
磁盤性能的瓶頸辅辩,總體優(yōu)化思路從兩個角度來看:
-
從手機廠商角度:
-
采用更大的內(nèi)存硬件配置难礼,并從總體上優(yōu)化系統(tǒng)的內(nèi)存占用。以防止出現(xiàn)系統(tǒng)低內(nèi)存狀態(tài)下玫锋,
Page Cache
頁緩存被回收后蛾茉,應用直接寫磁盤而導致的I/O
阻塞卡頓問題; -
啟用
Linux
的cgroup
機制對I/O
資源的進程隔離機制blkio
撩鹿,將進程按照優(yōu)先級進行分組(分為前臺進程谦炬、后臺進程、甚至可以根據(jù)需求進行更詳細的自定義劃分)节沦,以分配不同的I/O
帶寬資源键思,從而解決后臺應用進程搶占前臺應用I/O
資源造成的卡頓的問題; -
采用
f2fs
文件系統(tǒng)甫贯,替換ext4
文件系統(tǒng)吼鳞,以提升文件系統(tǒng)的性能;可以針對部分系統(tǒng)文件管理類的應用不走默認的fuse
路徑叫搁,而是走f2fs
赔桌,以提升其讀寫性能表現(xiàn); - 采用讀寫性能更強的閃存器件常熙,比如最新的
UFS 3.1
纬乍; -
啟用
fstrim
磁盤碎片整理功能,在息屏裸卫、充電等條件下觸發(fā)磁盤碎片整理的動作仿贬,從而減少磁盤碎片,提升磁盤讀寫性能墓贿;系統(tǒng)手機管家模塊可以引導用戶定期進行垃圾文件的清理茧泪,以保證磁盤有充足的剩余空間蜓氨; -
基于
mmap
內(nèi)存映射機制,將系統(tǒng)運行常訪問的文件Lock
鎖定到內(nèi)存緩存中队伟,以提升文件讀寫的性能穴吹,減少真正發(fā)生I/O
磁盤讀寫的概率,減輕系統(tǒng)的I/O
負載嗜侮。Android
系統(tǒng)框架中的PinnerService
服務主要職責就是這個港令,如下圖所示:
pinner.png
-
采用更大的內(nèi)存硬件配置难礼,并從總體上優(yōu)化系統(tǒng)的內(nèi)存占用。以防止出現(xiàn)系統(tǒng)低內(nèi)存狀態(tài)下玫锋,
2.從應用開發(fā)者角度:
-
選擇合適的文件讀寫操作。對讀寫速度有較高的要求锈颗,并允許低概率的數(shù)據(jù)丟失問題顷霹,就采用系統(tǒng)默認的基于
Page Cache
的緩存I/O
;對文件讀寫的速度要求不高击吱,但是需要嚴格的保證數(shù)據(jù)不會丟失就使用Direct I/O
淋淀;如果需要對同一塊區(qū)域進行頻繁讀寫的情況,對讀寫性能要求極高覆醇,可以采用mmap
內(nèi)存映射機制實現(xiàn)讀寫文件朵纷,比如騰訊開源的用于改善原生SharePreferences
機制性能的存儲工具MMKV
就是這個原理實現(xiàn)的。 -
I/O
方式的選擇永脓。一個是對于阻塞I/O
操作盡量放入子線程執(zhí)行袍辞,以免阻塞UI
線程;二是適當采用異步I/O
減少讀取文件的耗時憨奸,提升CPU
整體利用率革屠,比如Okio
就支持異步I/O
操作。 - 優(yōu)化數(shù)據(jù)結構排宰,建立內(nèi)存緩存海渊,盡量減少不必要的
I/O
挎袜;
3 編譯引起卡頓性能問題
編譯引起的性能問題主要分為兩類:一類是代碼的解釋執(zhí)行耗時蔼水,運行時VerifyClass
執(zhí)行耗時等引發(fā)卡頓氨距;二類是編譯本身引起的卡頓問題斋否,比如dex2oat
編譯搶占CPU
算力資源裙品,進程中的Jit
編譯線程搶占CPU
算力資源段直。
3.1 代碼解釋執(zhí)行耗時
一寓落、理論分析
Art
虛擬機有兩種代碼執(zhí)行模式:quick code
模式和 Interpreter
模式呛谜。對于dex
字節(jié)碼文件采用的是Interpreter
解釋執(zhí)行的模式在跳,每次執(zhí)行代碼,虛擬機需要將代碼轉換成機器指令集隐岛,然后交給CPU
去執(zhí)行猫妙,所以執(zhí)行效率比較低(早期Android
系統(tǒng)被詬病性能差的原因之一)。而對于經(jīng)過編譯(AOT
的dex2oat
編譯或JIT
運行時及時編譯聚凹,都需要消耗一定的磁盤空間存儲編譯出來的機器碼文件)后生成的ELF
格式機器碼文件(如.oat
格式文件割坠、.art
格式文件)齐帚,采用quick code
模式,直接執(zhí)行arm
匯編指令彼哼,執(zhí)行效率較高对妄。為了做到性能、磁盤空間占用以及應用安裝速度之間的最佳平衡敢朱,從Android 7.0
開始剪菱,采用AOT
+JIT
+解釋執(zhí)行的混合模式,其特點如下:
- 應用在安裝的時候
dex
中大部分函數(shù)代碼不會被編譯拴签。 -
App
運行時孝常,dex
文件先通過解析器被直接執(zhí)行,熱點函數(shù)會被識別并被JIT
編譯后存儲在jit code cache
中并生成profile
文件以記錄熱點函數(shù)的信息篓吁。 - 手機進入
IDLE
(空閑) 或者Charging
(充電) 狀態(tài)的時候茫因,系統(tǒng)會掃描App
目錄下的profile
文件并執(zhí)行AOT
過程進行編譯。
所以ART
虛擬機在執(zhí)行一個函數(shù)過程中會在 Interpreter
解釋器模式和quick code
模式切換杖剪,具體切換規(guī)則如下圖所示:
從上圖可以看出冻押,有些時候應用部分代碼還是按照
Interpreter
模式執(zhí)行,運行效率較低盛嘿,從而產(chǎn)生一些性能問題洛巢。
二、典型案例分析
問題描述:淘寶等三方應用冷啟動速度慢于同平臺競品機型次兆。
問題分析:結合Systrace
工具分析問題原因如下:
從與競品機器的對比Systrace
分析可以看到:同一個應用稿茉,且在兩臺手機CPU
型號和運行主頻一致的情況下,競品機器上應用冷啟動期間芥炭,UI
線程的Running
時長更短漓库,且Jit
工作線程上的熱點代碼編譯任務明顯少很多。從而可以推斷出园蝠,問題原因在于競品機器上的應用代碼大部分是以機器碼直接執(zhí)行渺蒿,而我們是以字節(jié)碼解釋執(zhí)行,所以效率更低彪薛,運行時間更長茂装。為什么會出現(xiàn)這個差異呢?后來對比日志分析發(fā)現(xiàn)善延,原因是競品機上應用第一次安裝打開后一段時間后少态,系統(tǒng)會觸發(fā)一次speed-profile
模式的dex2oat
編譯,會根據(jù)應用運行期間JIT
搜集到的熱點函數(shù)信息保存后生成的Profile
描述文件進行編譯易遣,從而將應用啟動期間的大部分代碼函數(shù)編譯成了機器碼.oat
文件彼妻,后續(xù)再啟動應用時即可直接以quick code
模式執(zhí)行機器碼,所以執(zhí)行效率更高训挡。
三澳骤、優(yōu)化思路
針對代碼解釋執(zhí)行造成的性能問題歧强,大致優(yōu)化思路是盡量讓應用代碼以機器碼形式運行,部分有效的手段有:
- 部分手機廠商为肮,在手機預置的應用商店中下載的三方應用摊册,除了
APK
文件外,還會包含保存有應用熱點函數(shù)信息的Profile
描述文件颊艳,這樣在應用dex2oat
安裝時茅特,選擇speed-profile
模式,就會根據(jù)Profile
描述文件棋枕,將應用的大部分熱點函數(shù)直接編譯成機器碼白修,從而極大的提升應用的性能表現(xiàn)。 - 在手機息屏充電重斑,進入
Idle
狀態(tài)后兵睛,通過JobScheduler
機制,啟動后臺服務窥浪,掃描App
目錄下的profile
文件并執(zhí)行AOT
過程進行編譯祖很,以達到手機越用越快的效果。
3.2 編譯本身耗時
一漾脂、理論分析
在dex2oat
進程編譯應用的過程中假颇,系統(tǒng)會啟動很多線程,消耗大量的CPU
算力資源骨稿”考Γ可能會導致前臺應用由于搶不到CPU
算力資源,其UI
線程長時間處于Runnable
狀態(tài)而卡頓坦冠。另外應用進程內(nèi)部形耗,Jit
工作線程動態(tài)編譯時持續(xù)運行,如果負載較重辙浑,持續(xù)跑到CPU
大核上趟脂,也會造成CPU
算力資源搶占。
二例衍、典型案例分析
問題描述:抖音應用界面嚴重卡頓。
問題分析:結合Systrace
工具分析問題原因如下:
從上圖可以看出:抖音應用界面卡頓的原因是因為其
UI
線程搶不到CPU
算力資源而長時間處于Runnbale
狀態(tài)已卸,而導致這個問題的很大一部分原因就是此時后臺dex2oat
進程創(chuàng)建多個線程持續(xù)執(zhí)行應用編譯動作搶占CPU
算力資源佛玄。
三、優(yōu)化思路
針對編譯本身耗時引起的性能問題累澡,部分有效的手段有:
- 通過
cpuset
配置梦抢,限制dex2oat
進程和Jit
工作線程對CPU
的使用,使其不搶占CPU
大核心算力資源愧哟,并設置參數(shù)限制其創(chuàng)建的線程數(shù)奥吩。 -
dex2oat
編譯應用的動作盡量放在設備息屏哼蛆,設備處于Idle
狀態(tài)下進行,以免影響前臺用戶的操作霞赫; - 限制三方應用在后臺觸發(fā)執(zhí)行
dex2oat
編譯動作(從Android 10
開始腮介,谷歌官方通過Selinux
權限的管控,禁止了三方應用觸發(fā)dex2oat
的權限)端衰;
參考
Linux系統(tǒng)中I/O操作的數(shù)據(jù)讀寫流程介紹 https://zhuanlan.zhihu.com/p/435406445
一文讀懂直播卡頓優(yōu)化那些事兒 https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247491520&idx=1&sn=dbd14f1d0d6107c87c137433ef435b5b&chksm=e9d0d422dea75d34e9788b0355a08132f84e558a6a7fc46bfb8a730f8be80ea77abee1c66e63&mpshare=1&scene=1&srcid=0328FFMqSCGN58JthxnZbEVw&sharer_sharetime=1648435053999&sharer_shareid=3accc00cd7dff7038611325ccc9f75bd&version=4.0.2.6061&platform=win#rd