1. 程式人生 > >Android卡頓優化:卡頓分析方法

Android卡頓優化:卡頓分析方法

rtm 主動 無法 基本 渲染 線程數 star 設備 當前

基礎知識
在具體講卡頓工具前,你需要了解一些基礎知識,它們主要都和 CPU 相關。造成卡頓的原因可能有千百種,不過最終都會反映到CPU 時間上。我們可以把 CPU 時間分為兩種:用戶時間和系統時間。用戶時間就是執行用戶態應用程序代碼所消耗的時間;系統時間就是執行內核態系統調用所消耗的時間,包括 I/O、鎖、中斷以及其他系統調用的時間。

  1. CPU 性能
    在開發過程中,我們可以通過下面的方法獲得設備的 CPU 信息。
    // 獲取 CPU 核心數
    cat /sys/devices/system/cpu/possible

    // 獲取某個 CPU 的頻率
    cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq

  2. 卡頓問題分析指標
    出現卡頓問題後,首先我們應該查看CPU 的使用率。怎麽查呢?我們可以通過/proc/stat
    得到整個系統的 CPU 使用情況,通過/proc/[pid]/stat
    可以得到某個進程的 CPU 使用情況。
    關於 stat 文件各個屬性的含義和 CPU 使用率的計算,你可以閱讀《Linux 環境下進程的 CPU 占用率》和Linux 文檔。其中比較重要的字段有:
    proc/self/stat:
    utime: 用戶時間,反應用戶代碼執行的耗時
    stime: 系統時間,反應系統調用執行的耗時
    majorFaults:需要硬盤拷貝的缺頁次數
    minorFaults:無需硬盤拷貝的缺頁次數

如果 CPU 使用率長期大於 60% ,表示系統處於繁忙狀態,就需要進一步分析用戶時間和系統時間的比例。對於普通應用程序,系統時間不會長期高於 30%,如果超過這個值,我們就應該進一步檢查是 I/O 過多,還是其他的系統調用問題。
Android 是站在 Linux 巨人的肩膀上,雖然做了不少修改也砍掉了一些工具,但還是保留了很多有用的工具可以協助我們更容易地排查問題,這裏我給你介紹幾個常用的命令。例如,top 命令可以幫助我們查看哪個進程是 CPU 的消耗大戶;vmstat 命令可以實時動態監視操作系統的虛擬內存和 CPU 活動;strace 命令可以跟蹤某個進程中所有的系統調用。
除了 CPU 的使用率,我們還需要查看CPU 飽和度。CPU 飽和度反映的是線程排隊等待 CPU 的情況,也就是 CPU 的負載情況。

CPU 飽和度首先會跟應用的線程數有關,如果啟動的線程過多,容易導致系統不斷地切換執行的線程,把大量的時間浪費在上下文切換,我們知道每一次 CPU 上下文切換都需要刷新寄存器和計數器,至少需要幾十納秒的時間。
我們可以通過使用vmstat
命令或者/proc/[pid]/schedstat
文件來查看 CPU 上下文切換次數,這裏特別需要註意nr_involuntary_switches
被動切換的次數。
proc/self/sched:
nr_voluntary_switches:
主動上下文切換次數,因為線程無法獲取所需資源導致上下文切換,最普遍的是 IO。
nr_involuntary_switches:
被動上下文切換次數,線程被系統強制調度導致上下文切換,例如大量線程在搶占 CPU。
se.statistics.iowait_count:IO 等待的次數
se.statistics.iowait_sum: IO 等待的時間

此外也可以通過 uptime 命令可以檢查 CPU 在 1 分鐘、5 分鐘和 15 分鐘內的平均負載。比如一個 4 核的 CPU,如果當前平均負載是 8,這意味著每個 CPU 上有一個線程在運行,還有一個線程在等待。一般平均負載建議控制在“0.7 × 核數”以內。
00:02:39 up 7 days, 46 min, 0 users,
load average: 13.91, 14.70, 14.32

另外一個會影響 CPU 飽和度的是線程優先級,線程優先級會影響 Android 系統的調度策略,它主要由 nice 和 cgroup 類型共同決定。nice 值越低,搶占 CPU 時間片的能力越強。當 CPU 空閑時,線程的優先級對執行效率的影響並不會特別明顯,但在 CPU 繁忙的時候,線程調度會對執行效率有非常大的影響。
關於線程優先級,你需要註意是否存在高優先級的線程空等低優先級線程,例如主線程等待某個後臺線程的鎖。從應用程序的角度來看,無論是用戶時間、系統時間,還是等待 CPU 的調度,都是程序運行花費的時間。
Android 卡頓排查工具
Traceview 和 systrace 都是我們比較熟悉的排查卡頓的工具,從實現上這些工具分為兩個流派。
第一個流派是 instrument。獲取一段時間內所有函數的調用過程,可以通過分析這段時間內的函數調用流程,再進一步分析待優化的點。
第二個流派是 sample。有選擇性或者采用抽樣的方式觀察某些函數調用過程,可以通過這些有限的信息推測出流程中的可疑點,然後再繼續細化分析。
這兩種流派有什麽差異?我們在什麽場景應該選擇哪種合適的工具呢?還有沒有其他有用的工具可以使用呢?下面我們一一來看。

  1. Traceview
    Traceview是我第一個使用的性能分析工具,也是吐槽的比較多的工具。它利用 Android Runtime 函數調用的 event 事件,將函數運行的耗時和調用關系寫入 trace 文件中。
    由此可見,Traceview 屬於 instrument 類型,它可以用來查看整個過程有哪些函數調用,但是工具本身帶來的性能開銷過大,有時無法反映真實的情況。比如一個函數本身的耗時是 1 秒,開啟 Traceview 後可能會變成 5 秒,而且這些函數的耗時變化並不是成比例放大。
    在 Android 5.0 之後,新增了startMethodTracingSampling
    方法,可以使用基於樣本的方式進行分析,以減少分析對運行時的性能影響。新增了 sample 類型後,就需要我們在開銷和信息豐富度之間做好權衡。

無論是哪種的 Traceview 對 release 包支持的都不太好,例如無法反混淆。其實 trace 文件的格式十分簡單,之前曾經寫個一個小工具,支持通過 mapping 文件反混淆 trace。

  1. systrace
    systrace是 Android 4.1 新增的性能分析工具。我通常使用 systrace 跟蹤系統的 I/O 操作、CPU 負載、Surface 渲染、GC 等事件。
    systrace 利用了 Linux 的ftrace調試工具,相當於在系統各個關鍵位置都添加了一些性能探針,也就是在代碼裏加了一些性能監控的埋點。Android 在 ftrace 的基礎上封裝了atrace,並增加了更多特有的探針,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。
    systrace 工具只能監控特定系統調用的耗時情況,所以它是屬於 sample 類型,而且性能開銷非常低。但是它不支持應用程序代碼的耗時分析,所以在使用時有一些局限性。
    由於系統預留了Trace.beginSection
    接口來監聽應用程序的調用耗時,那我們有沒有辦法在 systrace 上面自動增加應用程序的耗時分析呢?
    劃重點了,我們可以通過編譯時給每個函數插樁的方式來實現,也就是在重要函數的入口和出口分別增加Trace.beginSection
    和Trace.endSection
    。當然出於性能的考慮,我們會過濾大部分指令數比較少的函數,這樣就實現了在 systrace 基礎上增加應用程序耗時的監控。通過這樣方式的好處有:
    ? 可以看到整個流程系統和應用程序的調用流程。包括系統關鍵線程的函數調用,例如渲染耗時、線程鎖,GC 耗時等。
    ? 性能損耗可以接受。由於過濾了大部分的短函數,而且沒有放大 I/O,所以整個運行耗時不到原來的兩倍,基本可以反映真實情況。
    systrace 生成的是 HTML 格式的結果。

目前兩個工具都只支持 debugable 的應用程序,如果想測試 release 包,需要將測試機器 root。對於這個限制,我們在實踐中會專門打出 debugable 的測試包,然後自己實現針對 mapping 的反混淆功能。

隨著 Android 版本的演進,Google 不僅提供了更多的性能分析工具,而且也在慢慢優化現有工具的體驗,使功能更強大、使用門檻更低。而 Android Studio 則肩負另外一個重任,那就是讓開發者使用起來更加簡單的,圖形界面也更加直觀。
在 Android Studio 3.2 的 Profiler 中直接集成了幾種性能分析工具,其中:
? Sample Java Methods 的功能類似於 Traceview 的 sample 類型。
? Trace Java Methods 的功能類似於 Traceview 的 instrument 類型。
? Trace System Calls 的功能類似於 systrace。
? SampleNative (API Level 26+) 的功能類似於 Simpleperf。
坦白來說,Profiler 界面在某些方面不如這些工具自帶的界面,支持配置的參數也不如命令行,不過 Profiler 的確大大降低了開發者的使用門檻。
另外一個比較大的變化是分析結果的展示方式,這些分析工具都支持了 Call Chart 和 Flame Chart 兩種展示方式。下面我來講講這兩種展示方式適合的場景。

  1. Call Chart
    Call Chart 是 Traceview 和 systrace 默認使用的展示方式。它按照應用程序的函數執行順序來展示,適合用於分析整個流程的調用。舉一個最簡單的例子,A 函數調用 B 函數,B 函數調用 C 函數,循環三次,就得到了下面的 Call Chart。

Call Chart 就像給應用程序做一個心電圖,我們可以看到在這一段時間內,各個線程的具體工作,比如是否存在線程間的鎖、主線程是否存在長時間的 I/O 操作、是否存在空閑等。

  1. Flame Chart
    Flame Chart 也就是大名鼎鼎的火焰圖。它跟 Call Chart 不同的是,Flame Chart 以一個全局的視野來看待一段時間的調用分布,它就像給應用程序拍 X 光片,可以很自然地把時間和空間兩個維度上的信息融合在一張圖上。上面函數調用的例子,換成火焰圖的展示結果如下。

當我們不想知道應用程序的整個調用流程,只想直觀看出哪些代碼路徑花費的 CPU 時間較多時,火焰圖就是一個非常好的選擇。例如,之前我的一個反序列化實現非常耗時,通過火焰圖發現耗時最多的是大量 Java 字符串的創建和拷貝,通過將核心實現轉為 Native,最終使性能提升了很多倍。
火焰圖還可以使用在各種各樣的維度,例如內存、I/O 的分析。有些內存可能非常緩慢地泄漏,通過一個內存的火焰圖,我們就知道哪些路徑申請的內存最多,有了火焰圖我們根本不需要分析源代碼,也不需要分析整個流程。
最後我想說,每個工具都可以生成不同的展示方式,我們需要根據不同的使用場景選擇合適的方式。

Android卡頓優化:卡頓分析方法