Systrace?響應(yīng)速度實(shí)戰(zhàn)?1?:了解響應(yīng)速度原理
0. 性能工程
在介紹響應(yīng)速度的原理之前,這里先放一段 < 性能之巔 > 這本書中對(duì)于性能的描述,具體來(lái)說(shuō)就是方法論,非常貼合本文的主題,也強(qiáng)烈推薦各位搞性能優(yōu)化的同學(xué),把這本書作為手頭常讀的方法論書籍:
性能是充滿挑戰(zhàn)的
系統(tǒng)性能工程是一個(gè)充滿挑戰(zhàn)的領(lǐng)域,具體原因有很多,其中包括以下事實(shí),系統(tǒng)性能是主觀的、復(fù)雜的,而且常常是多問(wèn)題并存的
性能是主觀的
- 技術(shù)學(xué)科往往是客觀的,太多的業(yè)界人士審視問(wèn)題非黑即白。在進(jìn)行軟件故障查找的時(shí)候,判斷 bug 是否存在或 bug 是否修復(fù)就是這樣。bug 的出現(xiàn)總是伴隨著錯(cuò)誤信息,錯(cuò)誤信息通常容易解讀,進(jìn)而你就明白錯(cuò)誤為什么會(huì)出現(xiàn)了
- 與此不同,性能常常是主觀性的。開(kāi)始著手性能問(wèn)題的時(shí)候,對(duì)問(wèn)題是否存在的判斷都有可能是模糊的,在問(wèn)題被修復(fù)的時(shí)候也同樣,被一個(gè)用戶認(rèn)為是 “不好” 的性能,另一個(gè)用戶可能認(rèn)為是 “好” 的
系統(tǒng)是復(fù)雜的
- 除了主觀性之外,性能工程作為一門充滿了挑戰(zhàn)的學(xué)科,除了因?yàn)橄到y(tǒng)的復(fù)雜性,還因?yàn)閷?duì)于性能,我們常常缺少一個(gè)明確的分析起點(diǎn)。有時(shí)我們只是從猜測(cè)開(kāi)始,比如,責(zé)怪網(wǎng)絡(luò),而性能分析必須對(duì)這是不是一個(gè)正確的方向做出判斷
- 性能問(wèn)題可能出在子系統(tǒng)之間復(fù)雜的互聯(lián)上,即便這些子系統(tǒng)隔離時(shí)表現(xiàn)得都很好。也可能由于連鎖故障(cascading failure)出現(xiàn)性能問(wèn)題,這指的是一個(gè)出現(xiàn)故障的組件會(huì)導(dǎo)致其他組件產(chǎn)生性能問(wèn)題。要理解這些產(chǎn)生的問(wèn)題,你必須理清組件之間的關(guān)系,還要了解它們是怎樣協(xié)作的
- 瓶頸往往是復(fù)雜的,還會(huì)以意想不到的方式互相聯(lián)系。修復(fù)了一個(gè)問(wèn)題可能只是把瓶頸推向了系統(tǒng)里的其他地方,導(dǎo)致系統(tǒng)的整體性能并沒(méi)有得到期望的提升。
- 除了系統(tǒng)的復(fù)雜性之外,生產(chǎn)環(huán)境負(fù)載的復(fù)雜特性也可能會(huì)導(dǎo)致性能問(wèn)題。在實(shí)驗(yàn)室環(huán)境很難重現(xiàn)這類情況,或者只能間歇式地重現(xiàn)
- 解決復(fù)雜的性能問(wèn)題常常需要全局性的方法。整個(gè)系統(tǒng) —— 包括自身內(nèi)部和外部的交互 —— 都可能需要被調(diào)查研究。這項(xiàng)工作要求有非常廣泛的技能,一般不太可能集中在一人身上,這促使性能工程成為一門多變的并且充滿智力挑戰(zhàn)的工作
可能有多個(gè)問(wèn)題并存
-- 以上幾段內(nèi)容摘錄自 < 性能之巔 >
- 找到一個(gè)性能問(wèn)題點(diǎn)往往并不是問(wèn)題本身,在復(fù)雜的軟件中通常會(huì)有多個(gè)問(wèn)題
- 性能分析的又一個(gè)難點(diǎn):真正的任務(wù)不是尋找問(wèn)題,而是辨別問(wèn)題或者說(shuō)是辨別哪些問(wèn)題是最重要的
- 要做到這一點(diǎn),性能分析必須量化(quantify)問(wèn)題的重要程度。某些性能問(wèn)題可能并不適用于你的工作負(fù)載或者只在非常小的程度上適用。理想情況下,你不僅要量化問(wèn)題,還要估計(jì)每個(gè)問(wèn)題修復(fù)后能帶來(lái)的增速。當(dāng)管理層審查工程或運(yùn)維資源的開(kāi)銷緣由時(shí),這類信息尤其有用。
- 有一個(gè)指標(biāo)非常適合用來(lái)量化性能,那就是 延時(shí)(latency)
1. 響應(yīng)速度概述
響應(yīng)速度是應(yīng)用 App 性能的重要指標(biāo)之一。響應(yīng)慢通常表現(xiàn)為點(diǎn)擊效果延遲、操作等待或白屏?xí)r間長(zhǎng)等,主要場(chǎng)景包括:
- 應(yīng)用啟動(dòng)場(chǎng)景,包括冷啟動(dòng)、熱啟動(dòng)、溫啟動(dòng)等
- 界面跳轉(zhuǎn)場(chǎng)景,包括應(yīng)用內(nèi)頁(yè)面跳轉(zhuǎn)、App 之間跳轉(zhuǎn)
- 其他非跳轉(zhuǎn)的點(diǎn)擊場(chǎng)景(開(kāi)關(guān)、彈窗、長(zhǎng)按、控件選擇、單擊、雙擊等)
- 亮滅屏、開(kāi)關(guān)機(jī)、解鎖、人臉識(shí)別、拍照、視頻加載等場(chǎng)景
- 系統(tǒng)開(kāi)發(fā)者 往往從 input 中斷開(kāi)始看,部分以應(yīng)用第一幀為結(jié)束點(diǎn)(因?yàn)楸容^好計(jì)算),部分以應(yīng)用加載完成為結(jié)束點(diǎn)(比較主觀,除非結(jié)束點(diǎn)比較容易通過(guò)工具去判斷),主要是以優(yōu)化應(yīng)用的整體性能為主,涉及到的方面就比較廣,包括 input 事件傳遞、SystemServer、SurfaceFlinger、Kernel 、Launcher 等
- App 開(kāi)發(fā)者 一般從 Application 的 onCreate 或者 attachContext 開(kāi)始看,大部分以界面完全加載或者用戶可操作為結(jié)束點(diǎn),因?yàn)槭亲约旱膽?yīng)用,結(jié)束點(diǎn)在代碼里面可以主動(dòng)加,主要還是以優(yōu)化應(yīng)用自身的啟動(dòng)速度為主,市面上講啟動(dòng)速度優(yōu)化的,大部分是講這部分
- 測(cè)試同學(xué) 則更多從用戶的真實(shí)體驗(yàn)角度來(lái)看,以桌面點(diǎn)擊應(yīng)用圖標(biāo)且應(yīng)用圖標(biāo)變色為第一幀,內(nèi)容完全加載為結(jié)束點(diǎn)。測(cè)試過(guò)程一般使用 高速相機(jī) 自動(dòng)化,通過(guò)機(jī)械手和圖形識(shí)別技術(shù),可以自動(dòng)進(jìn)行響應(yīng)速度測(cè)試并抓取相關(guān)的測(cè)試數(shù)據(jù)
2. 響應(yīng)速度問(wèn)題分析思路
2.1 分清起點(diǎn)和終點(diǎn)
分析響應(yīng)速度,最重要的是要找到起點(diǎn)和終點(diǎn),上一節(jié)講到,不同角色的開(kāi)發(fā)者,對(duì)這個(gè)性能指標(biāo)的判定起點(diǎn)和終點(diǎn)都不一樣;而且這個(gè)指標(biāo)有很主觀的成分,所以在開(kāi)始的時(shí)候,就要跟各方來(lái)確定好起點(diǎn)和終點(diǎn),具體的數(shù)值標(biāo)準(zhǔn),下面一些手段可以幫助大家來(lái)確定
- 競(jìng)品分析。一般來(lái)說(shuō),響應(yīng)速度這個(gè)指標(biāo)都會(huì)有一個(gè)對(duì)標(biāo)的競(jìng)品,競(jìng)品手機(jī)或者競(jìng)品 App,相同的條件下,競(jìng)品手機(jī)或者競(jìng)品 App 從點(diǎn)擊到響應(yīng)花費(fèi)了多少時(shí)間,可以作為一個(gè)標(biāo)準(zhǔn)
- 對(duì)比前一個(gè)版本。有時(shí)候系統(tǒng)進(jìn)行大版本升級(jí)或者 App 進(jìn)行版本迭代,那么上一個(gè)版本的數(shù)據(jù)就可以拿來(lái)作為標(biāo)準(zhǔn)進(jìn)行對(duì)比
2.2 響應(yīng)速度常見(jiàn)問(wèn)題
2.2.1 Android 系統(tǒng)自身原因?qū)е马憫?yīng)慢
下面這些列舉的是 Android 系統(tǒng)自身的原因,與 Android 機(jī)器的性能有比較大的關(guān)系,性能越差,越容易出現(xiàn)響應(yīng)速度問(wèn)題。下面就列出了 Android 系統(tǒng)原因?qū)е碌?App 響應(yīng)速度出現(xiàn)問(wèn)題的原因,以及這個(gè)時(shí)候 App 端在 Systrace 中的表現(xiàn)
CPU 頻率不足
App 端的表現(xiàn):主線程處于 Running 狀態(tài),但是執(zhí)行耗時(shí)變長(zhǎng)
CPU 大小核調(diào)度:關(guān)鍵任務(wù)跑到了小核
App 端的表現(xiàn):Systrace 看主線程處于 Running 狀態(tài),但是執(zhí)行耗時(shí)變長(zhǎng)
SystemServer 繁忙,主要影響
- 響應(yīng) App 主線程 Binder 調(diào)用處理耗時(shí)
- App 端的表現(xiàn):Systrace 看主線程處于 Sleep 狀態(tài),在等待 Binder 調(diào)用返回
- 應(yīng)用啟動(dòng)過(guò)程邏輯處理耗時(shí)
- App 端的表現(xiàn):Systrace 看主線程處于 Sleep 狀態(tài),在等待 Binder 調(diào)用返回
SurfaceFlinger 繁忙
主要影響應(yīng)用的渲染線程的 dequeueBuffer、queueBuffer
- App 端的表現(xiàn):Systrace 看應(yīng)用渲染線程的 dequeueBuffer、queueBuffer 處于 Binder 等待狀態(tài)
系統(tǒng)低內(nèi)存 [3]
低內(nèi)存的時(shí)候,很大概率出現(xiàn)下面幾種情況,都會(huì)對(duì) SystemServer 和應(yīng)用有影響
- 低內(nèi)存的時(shí)候,有些應(yīng)用會(huì)頻繁被殺和啟動(dòng),而應(yīng)用啟動(dòng)時(shí)一個(gè)重操作,會(huì)占用 CPU 資源,導(dǎo)致前臺(tái) App 啟動(dòng)變慢
- App 端的表現(xiàn):Systrace 看應(yīng)用主線程 Runnable 狀態(tài)變多,Running 狀態(tài)變少,整體函數(shù)執(zhí)行耗時(shí)增加
- 低內(nèi)存的時(shí)候,, 很容易觸發(fā)各個(gè)進(jìn)程的 GC , , 用于內(nèi)存回收的 HeapTaskDeamon、kswapd0 出現(xiàn)非常頻繁
- App 端的表現(xiàn):Systrace 看應(yīng)用主線程 Runnable 狀態(tài)變多,Running 狀態(tài)變少,整體函數(shù)執(zhí)行耗時(shí)增加
- 低內(nèi)存會(huì)導(dǎo)致磁盤 IO 變多,如果頻繁進(jìn)行磁盤 IO , 由于磁盤 IO 很慢,那么主線程會(huì)有很多進(jìn)程處于等 IO 的狀態(tài),也就是我們經(jīng)??吹降?Uninterruptible Sleep
- App 端的表現(xiàn):Systrace 看應(yīng)用主線程 Uninterruptible Sleep 和 Uninterruptible Sleep - IO 狀態(tài)變多,Running 狀態(tài)變少,整體函數(shù)執(zhí)行耗時(shí)增加
系統(tǒng)觸發(fā)溫控
頻率被限制:由于溫度過(guò)高,CPU 最高頻率被限制
整機(jī) CPU 繁忙
可能有多個(gè)高負(fù)載進(jìn)程同時(shí)在運(yùn)行,或者有單個(gè)進(jìn)程負(fù)載過(guò)高跑滿了 CPU
2.2.2 應(yīng)用自身原因?qū)е马憫?yīng)慢
應(yīng)用自身原因主要是應(yīng)用啟動(dòng)時(shí)候的組件初始化、View 初始化、數(shù)據(jù)初始化耗時(shí)等,具體包括:
- Application.onCreate:應(yīng)用自身的邏輯 三方 SDK 初始化耗時(shí)
- Activity 的生命周期函數(shù):onStart、onCreate、onResume 耗時(shí)
- Services 的生命周期函數(shù)耗時(shí)
- Broadcast 的 onReceive 耗時(shí)
- ContentProvider 初始化耗時(shí)(注意已經(jīng)被濫用)
- 界面布局初始化:measure、layout、draw 等耗時(shí)
- 渲染線程初始化:setSurface、queueBuffer、dequeueBuffer、Textureupload 等耗時(shí)
- Activity 跳轉(zhuǎn):從 SplashActivity 到 MainActivity 耗時(shí)
- 應(yīng)用向主線程 post 的耗時(shí) Message 耗時(shí)
- 主線程或者渲染線程等待子線程數(shù)據(jù)更新耗時(shí)
- 主線程或者渲染線程等待子進(jìn)程程數(shù)據(jù)更新耗時(shí)
- 主線程或者渲染線程等待網(wǎng)絡(luò)數(shù)據(jù)更新耗時(shí)
- 主線程或者渲染線程 binder 調(diào)用耗時(shí)
- WebView 初始化耗時(shí)
- 初次運(yùn)行 JIT 耗時(shí)
2.3 響應(yīng)速度問(wèn)題分析套路(以 Systrace 為主)
2.3.1 確認(rèn)前提條件
確認(rèn)前提條件 (老化,數(shù)據(jù)量、下載等)、操作步驟、問(wèn)題現(xiàn)象,本地復(fù)現(xiàn)步驟
2.3.2 需要明確測(cè)試標(biāo)準(zhǔn)
- 啟動(dòng)時(shí)間的起點(diǎn)是哪里
- 啟動(dòng)時(shí)機(jī)的終點(diǎn)是哪里
2.3.3 抓取所需的日志信息
主要包括 Systrace、常規(guī) log 、視頻、截圖等
2.3.4 分析 Systrace
首先進(jìn)行 Systrace 分析,大概找出差異的點(diǎn)
2.3.4.1 分析耗時(shí)差異
首先查看應(yīng)用耗時(shí)點(diǎn),分析對(duì)比機(jī)差異,這里可以把應(yīng)用啟動(dòng)階段分成好幾段來(lái)看,來(lái)對(duì)比分析是哪部分時(shí)間增加
- Application 創(chuàng)建
- Activity 創(chuàng)建
- 第一個(gè) doFrame
- 后續(xù)內(nèi)容加載
- 應(yīng)用自己的 Message
2.3.4.2 分析應(yīng)用耗時(shí)的點(diǎn)
- 是否某一段方法自身執(zhí)行耗時(shí)比較久(Running 狀態(tài)) --> 應(yīng)用自身問(wèn)題
- 主線程是否有大段 Running 狀態(tài),但是底下沒(méi)有任何堆棧 --> 應(yīng)用自身問(wèn)題,加 TraceTag 或者使用 TraceView 來(lái)找到對(duì)應(yīng)的代碼邏輯
- 是否在等 Binder 耗時(shí)比較久(Sleep 狀態(tài)) --> 檢測(cè) Binder 服務(wù)端,一般是 SystemServer
- 是否在等待子線程返回?cái)?shù)據(jù)(Sleep 狀態(tài)) --> 應(yīng)用自身問(wèn)題,通過(guò)查看 wakeup 信息,來(lái)找到依賴的子線程
- 是否在等待子進(jìn)程返回?cái)?shù)據(jù)(Sleep 狀態(tài)) --> 應(yīng)用自身問(wèn)題,通過(guò)查看 wakeup 信息,來(lái)找到依賴的子進(jìn)程或者其他進(jìn)程 (一般是 ContentProvider 所在的進(jìn)程)
- 是否有大量的 Runnable --> 系統(tǒng)問(wèn)題,查看 CPU 部分,看看是否已經(jīng)跑滿
- 是否有大量的 IO 等待(Uninterruptible Sleep | WakeKill - Block I/O) --> 檢查系統(tǒng)是否已經(jīng)低內(nèi)存 [4]
- RenderThread 是否執(zhí)行 dequeueBuffer 和 queueBuffer 耗時(shí) --> 查看 SurfaceFlinger
2.3.4.3 排查系統(tǒng)問(wèn)題
如果分析是系統(tǒng)的問(wèn)題,則根據(jù)上面耗時(shí)的點(diǎn),查看系統(tǒng)對(duì)應(yīng)的部分,一般情況要優(yōu)先查看系統(tǒng)是否異常,參考上面列出的的系統(tǒng)原因,主要看下面四個(gè)區(qū)域(Systrace)
- Kernel 區(qū)域 [5]
- 查看關(guān)鍵任務(wù)是否跑在了小核 --> 一般小核是 0-3(也有特例),如果啟動(dòng)時(shí)候的關(guān)鍵任務(wù)跑到了小核,執(zhí)行速度也會(huì)變慢
- 查看頻率是否沒(méi)有跑滿 --> 表現(xiàn)是核心頻率沒(méi)有達(dá)到最大值,比如最大值是 2.8Ghz,但是只跑到了 1.8Ghz,那么可能是有問(wèn)題的
- 查看 CPU 使用率,是否已經(jīng)跑滿了 --> 表現(xiàn)是 CPU 區(qū)域八個(gè)核心上,任務(wù)和任務(wù)之間沒(méi)有空隙
- 查看是否處于低內(nèi)存狀態(tài) [6],比如是否應(yīng)用進(jìn)程狀態(tài)有大量的 Uninterruptible Sleep | WakeKill - Block I/O、HeapTaskDeamon 任務(wù)執(zhí)行頻繁、kswapd0 任務(wù)執(zhí)行頻繁等
- SystemServer 進(jìn)程區(qū)域 [7]
- input 事件讀取和分發(fā)是否有異常 --> 表現(xiàn)是 input 事件傳遞耗時(shí),比較少見(jiàn)
- binder 執(zhí)行是否耗時(shí) --> 表現(xiàn)是 SystemServer 對(duì)應(yīng)的 Binder 執(zhí)行代碼邏輯耗時(shí)
- binder 等 am、wm 鎖是否耗時(shí) --> 表現(xiàn)是 SystemServer 對(duì)應(yīng)的 Binder 都在等待鎖,可以通過(guò) wakeup 信息跟蹤等鎖情況,分析等鎖是不是由于應(yīng)用導(dǎo)致的
- 是否有應(yīng)用頻繁啟動(dòng)或者被殺 --> 在 Systrace 中查看 startProcess,或者查看 Event Log
- SurfaceFlinger 進(jìn)程區(qū)域 [8]
- dequeueBuffer 和 queueBuffer 是否執(zhí)行耗時(shí) --> 表現(xiàn)是 SurfaceFlinger 的對(duì)應(yīng)的 Binder 執(zhí)行 dequeueBuffer 和 queueBuffer 耗時(shí)
- 主線程是否執(zhí)行耗時(shí) --> 表現(xiàn)是 SurfaceFlinger 主線程耗時(shí),可能是在執(zhí)行其他的任務(wù)
- Launcher 進(jìn)程區(qū)域(冷熱啟動(dòng)場(chǎng)景)
- Launcher 進(jìn)程處理點(diǎn)擊事件是否耗時(shí) --> 表現(xiàn)在處理 input 事件耗時(shí)
- Launcher 自身 pause 是否耗時(shí) --> 表現(xiàn)在執(zhí)行 onPause 耗時(shí)
- Launcher 應(yīng)用啟動(dòng)動(dòng)畫是否耗時(shí)或者卡頓 --> 表現(xiàn)在動(dòng)畫耗時(shí)或者卡頓
2.3.4.4 初步分析有懷疑的點(diǎn)之后
- 如果是系統(tǒng)的原因,首先需要看應(yīng)用自身是否能規(guī)避,如果不能規(guī)避,則轉(zhuǎn)給系統(tǒng)來(lái)處理
- 如果是應(yīng)用自身的原因,可以使用 TraceView(AS 自帶的 CPU Profiler)、Simple Perf 等繼續(xù)查看更加詳細(xì)的函數(shù)調(diào)用信息,也可以使用 TraceFix 插件 [9],插入更多的 TraceTag 之后,重新抓取 Systrace 來(lái)對(duì)比分析
2.3.4.5 問(wèn)題可能有很多個(gè)原因
- 首先要把影響最大的因素找出來(lái)優(yōu)化,影響比較小的因素可以先忽略
- 有些問(wèn)題需要系統(tǒng)的配合才能解決,這時(shí)候需要跟系統(tǒng)一起進(jìn)行調(diào)優(yōu)(比如各大 App 廠商就會(huì)有專門跟手機(jī)廠商打交道的,手機(jī)廠商會(huì)以 SDK 的形式,暴露部分系統(tǒng)接口給 App 來(lái)使用,比如 Oppo 、華為、Vivo 等)
- 有些問(wèn)題影響很小或者無(wú)解,這時(shí)候需要跟測(cè)試同學(xué)溝通清楚
- 有些問(wèn)題是重復(fù)問(wèn)題或不同平臺(tái)的相同,可以在 Bug 庫(kù)中搜索是否有案例
End
本篇文章主要是一個(gè)響應(yīng)速度基礎(chǔ)知識(shí)方面的一個(gè)普及,其中涉及到大量的系統(tǒng)知識(shí),不熟悉的同學(xué)可以跟著 Systrace 基礎(chǔ)知識(shí)系列 [10] 過(guò)一下