經作者授權,發表Tieto某青年牛的一篇《程式設計師》大作。
Android系統效能調優工具介紹
在軟體開發過程中,想必很多讀者都遇到過系統效能問題。而解決系統效能問題的幾個主要步驟是:
- 測評:對系統進行大量有針對性的測試,以得到合適的測試資料。
- 分析系統瓶頸:分析測試資料,找到其中的hotspot(熱點,即bottleneck)。
- 效能優化:對hotspot相關的程式碼進行優化。
由上述步驟可知,效能優化的目標物件是hotspot。如果找到的hotspot並非真正的熱點,則效能優化的結果必然是事倍功半甚至竹籃打水一場空。所以,作為Android效能調優相關知識的第一部分,本篇首先將向讀者介紹Android平臺中三個重要的效能測試工具,它們能很好得幫助開發者找到hotspot。
一Traceview介紹
1.1 Traceview簡介
Traceview是Android平臺特有的資料採集和分析工具,它主要用於分析Android中應用程式的hotspot。Traceview本身只是一個資料分析工具,而資料的採集則需要使用Android SDK中的Debug類或者利用DDMS工具。二者的用法如下:
- 開發者在一些關鍵程式碼段開始前呼叫Android SDK中Debug類的startMethodTracing函式,並在關鍵程式碼段結束前呼叫stopMethodTracing函式。這兩個函式執行過程中將採集執行時間內該應用所有執行緒(注意,只能是Java執行緒)的函式執行情況,並將採集資料儲存到/mnt/sdcard/下的一個檔案中。開發者然後需要利用SDK中的Traceview工具來分析這些資料。
- 藉助Android SDK中的DDMS工具。DDMS可採集系統中某個正在執行的程式的函式呼叫資訊。對開發者而言,此方法適用於沒有目標應用原始碼的情況。DDMS工具中Traceview的使用如圖1-1所示。
圖1-1 DDMS中Traceview使用示意圖
點選圖1-1中所示按鈕即可以採集目標程式的資料。當停止採集時,DDMS會自動觸發Traceview工具來瀏覽採集資料。
下面,我們通過一個示例程式向讀者介紹Debug類以及Traceview的使用。
1.2 Traceview示例分析
示例程式執行時的介面如圖1-2所示:
圖1-2 示例介面圖
圖1-2中:
- SystraceDemoStringAAA等字樣是TraceviewDemo程式啟動時ListView最初顯示的字串。
- 當使用者點選ListView中的某一項時,Traceview將計算對應項當前顯示的字串的MD5值40次,然後用計算得到的MD5字串替換該項之前顯示的內容。其效果如圖1-2中的“MD5值“箭頭所示。
該示例的關鍵程式碼如圖1-3所示:
圖1-3示例程式碼
由圖1-3可知:
- 左圖中,Debug類的startMethodTracing和stopMethodTracing分別在MainAcvtivity的構造方法和onDestroy函式中呼叫。
- onCreate函式中我們設定了第一個hotspot,即getStringToShow函式。它將解析一個XML檔案,並將解析後的字串儲存到mListItem中以作為ListView的顯示內容。
- 右圖中,當使用者點選ListView中的某個Item時,程式在onListItem中將計算MD5值40次,然後用計算結果做為被點選項的新字串顯示。generateMD5中的函式是本示例的第二個hotspot。
現在,我們用Traceview工具將測試結果檔案TraceviewDemo.trace開啟。
Traceview介面比較複雜,其UI劃分為上下兩個皮膚,即Timeline Panel(時間線皮膚)和Profile Panel(分析皮膚)。圖1-4所示為Timeline Panel介面:
圖1-4 Traceview Timeline Panel示意圖
圖1-4中的Timeline Panel又可細分為左右兩個Pane:
- 左邊Pane顯示的是測試資料中所採集的執行緒資訊。由圖1-4可知,本次測試資料採集了main執行緒,兩個Binder執行緒和其它系統輔助執行緒(例如GC執行緒等)的資訊。
- 右邊Pane所示為時間線,時間線上是每個執行緒測試時間段內所涉及的函式呼叫資訊。這些資訊包括函式名、函式執行時間等。由圖1-4可知,main執行緒對應行的的內容非常豐富,而其他執行緒在這段時間內幹得工作則要少得多。
- 另外,開發者可以在時間線Pane中移動時間線縱軸。縱軸上邊將顯示當前時間點中某執行緒正在執行的函式資訊。
現在來看Traceview的Profile Panel介面,如圖1-5所示:
圖1-5 TraceviewProfile Panel介面
Profile Panel是Traceview的核心介面,其內涵非常豐富。它主要展示了某個執行緒(先在Timeline Panel中選擇執行緒)中各個函式呼叫的情況,包括CPU使用時間、呼叫次數等資訊。而這些資訊正是查詢hotspot的關鍵依據。所以,對開發者而言,一定要了解Profile Panel中各列的含義。筆者總結了其中幾個重要列的作用,如表1-1所示:
表1-1 Profile Panel各列作用說明
列名 |
描述 |
Name |
該執行緒執行過程中所呼叫的函式名 |
Incl Cpu Time |
某函式佔用的CPU時間,包含內部呼叫其它函式的CPU時間 |
Excl Cpu Time |
某函式佔用的CPU時間,但不含內部呼叫其它函式所佔用的CPU時間 |
Incl Real Time |
某函式執行的真實時間(以毫秒為單位),內含呼叫其它函式所佔用的真實時間 |
Excl Real Time |
某函式執行的真實時間(以毫秒為單位),不含呼叫其它函式所佔用的真實時間 |
Call+Recur Calls/Total |
某函式被呼叫次數以及遞迴呼叫佔總呼叫次數的百分比 |
Cpu Time/Call |
某函式呼叫CPU時間與呼叫次數的比。相當於該函式平均執行時間 |
Real Time/Call |
同CPU Time/Call類似,只不過統計單位換成了真實時間 |
另外,每一個Time列還對應有一個用時間百分比來統計的列(如Incl Cpu Time列對應還有一個列名為Incl Cpu Time %的列,表示以時間百分比來統計的Incl Cpu Time)。
瞭解完Traceview的UI後,現在介紹如何利用Traceview來查詢hotspot。
一般而言,hotspot包括兩種型別的函式:
- 一類是呼叫次數不多,但每次呼叫卻需要花費很長時間的函式。在示例程式碼中,它就是hotspot 1。
- 一類是那些自身佔用時間不長,但呼叫卻非常頻繁的函式。在示例程式碼中,它就是hotspot 2。
首先,我們來查詢hotspot 1。
在Profile Panel中,選擇按Cpu Time/Call進行降序排序(從上之下排列,每項的耗費時間由高到低),得到如圖1-6所示的結果:
圖1-6 按CPU Time/Call降序排列資料
圖1-6中:
- MainActivity.onCreate是應用程式中的函式,它耗時為4618.684。然後,點選MainActivity.onCreate項,得到箭頭所示的小圖。
- 小圖中,Parents一行顯示的是MainActivity.onCreate的呼叫者,本例中它是performCreate函式。這部分程式碼屬於Framework部分。Children行顯示的是MainActivity.onCreate呼叫的子函式。
- 在MainActivity.onCreate呼叫的子函式中,我們發現getStringsToShow在Incl Cpu Time %一列中佔據了63.3%,它是onCreate子函式耗費時間最長的,而且Calls+Recur Calls/Total列顯示其呼叫次數為1,即它僅僅被呼叫一次了。這個函式是應用程式實現的,所以極有可能是一個潛在的Hotspot。
- 另外,由於筆者已經知道getStringsToShow是示例應用自己實現的函式,故在圖1-6的大圖中,可直接根據MainActivity.getStringsToShow花費了2921.913CPU時間這個資訊來確定Hotspot就是它。
相對來說,型別1的hotspot比較好找,步驟是先按降序對時間項進行排列(可以是時間百分比、真實時間或CPU時間),然後查詢耗費時間最多的函式。一般而言,先應對應用程式自己實現的函式進行排查,Framework的函式也有可能是hotspot,但主因一般還是在應用本身(例如設定複雜的介面,導致對應XML解析非常慢)。
現在,我們來看如何查詢型別2的hotspot。
點選Call/Recur Calls/Total列頭,使之按降序排列。關注點放在那些呼叫頻繁並且佔用資源較多的函式。圖1-7為降序排列的結果圖。
圖1-7型別2 Hotspot查詢過程示意之一
圖1-7所示的執行最頻繁的幾個函式中,我們發現了幾個懷疑點,由圖中的1和2箭頭標示。
- 結合程式碼,箭頭1所指的函式在程式碼中實際並不存在。這是因為程式碼中直接訪問了內部類的私有成員,導致java編譯器在編譯時自動生成了這個函式。這個函式的呼叫次數非常多。所以,為了提高效率,我們可以修改內部類成員的訪問型別定義為public。不過,該函式的Incl Cpu Time並不高,只有3.2%。
- 同樣,箭頭2所指部分的函式呼叫次數也很多,達到了5888多次。不過它們佔用的時間百分比只有0.9%。
第一次查詢的潛在點被排除後,繼續瀏覽資料,得到如圖1-8所示的結果。
圖1-8 型別2 Hotspot查詢過程示意之二
在圖1-8中:
- 紅框處有兩個過載的MyMD5.getHashString函式呼叫,它們各執行了368次,而且佔用的CPU時間百分比達到了31.8%和53.2%。很顯然,這2處呼叫就有優化的餘地,這就是我們所懷疑的hotspot2。
找到hotspot之後,開發者就需要結合程式碼來進行對應的優化了。關於Java程式碼優化,讀者可參考如下資料:http://developer.android.com/training/articles/perf-tips.html
總體而言,Hotspot的查詢是一個細緻的工作,需要開發者對目標程式的程式碼,以及Traceview工具都比較熟悉才行。
1.3 Traceview小結
Traceview工具是Android平臺應用程式效能分析的利器。不過筆者覺得它的UI還是有些複雜。並且使用時感覺流暢度不夠好。
Google官方關於Traceview的介紹可參考以下連結,不過其內容以及較久未更新了。http://developer.android.com/tools/debugging/debugging-tracing.html。
二Systrace介紹
2.1 Systrace簡介
Systrace是Android4.1中新增的效能資料取樣和分析工具。它可幫助開發者收集Android關鍵子系統(如surfaceflinger、WindowManagerService等Framework部分關鍵模組、服務)的執行資訊,從而幫助開發者更直觀的分析系統瓶頸,改進效能。
Systrace的功能包括跟蹤系統的I/O操作、核心工作佇列、CPU負載以及Android各個子系統的執行狀況等。在Android平臺中,它主要由3部分組成:
- 核心部分:Systrace利用了Linux Kernel中的ftrace功能。所以,如果要使用Systrace的話,必須開啟kernel中和ftrace相關的模組。
- 資料採集部分:Android定義了一個Trace類。應用程式可利用該類把統計資訊輸出給ftrace。同時,Android還有一個atrace程式,它可以從ftrace中讀取統計資訊然後交給資料分析工具來處理。
- 資料分析工具:Android提供一個systrace.py(python指令碼檔案,位於Android SDK目錄/tools/systrace中,其內部將呼叫atrace程式)用來配置資料採集的方式(如採集資料的標籤、輸出檔名等)和收集ftrace統計資料並生成一個結果網頁檔案供使用者檢視。
從本質上說,Systrace是對Linux Kernel中ftrace的封裝。應用程式需要利用Android提供的Trace類來使用Systrace。Android 4.1為系統中的幾個關鍵程式和模組都新增了Systrace功能。以顯示系統中重要模組Hwcomposer為例,其程式碼中使用Systrace的方法如圖2-1所示:
圖2-1 Hwcomposer模組Systrace使用示例
圖2-1中,應用程式只要通過三個巨集就可使用Systrace了:
- 定義ATRACE_TAG:Hwcomposer使用了ATRACE_TAG_GRAPHICS,表示它和Graphics相關。
- ATRACE_INIT:用於統計某個變數使用的情況。下文將見到程式碼中”VSYNC”的統計結果。
- ATRACE_CALL:用於統計函式的呼叫情況。
由於篇幅關係,關於Trace使用更多的資訊請讀者閱讀frameworks/native/include/utils/Trace.h或者android.os.Trace類。下面,我們通過一個示例來展示Systrace的使用。
2.2 Systrace例項
首先,在PC機上執行如下命令以啟動Systrace,如圖2-2所示:
圖2-2 Systrace操作步驟
執行上述命令後,將得到一個名為trace.html的檔案(trace.html是預設檔名,讀者也可在命令列中指定其他檔名)。通過瀏覽器開啟此檔案,結果如圖2-3所示:
圖 2-3 trace.html內容示意
圖2-3中所示的trace.html頁面內容和Traceview的Timeline Panel非常類似。圖中包含的內容如下:
- 由於在systrace.py中指定了-f -l和-i引數,Systrace將生成CPU頻率、負載和狀態相關的資訊。它們為圖2-1中第一個紅框所示。由於筆者所測手機CPU為雙核,故圖中有CPU 0和CPU 1之分。為行文方便,筆者用CPU N來指代CPU的某個核。
- “CPU N“所示行對應於整個測試時間內,某個核上執行的程式資訊。
- “CPU N C-State“所示行為整個測試時間內,某個CPU狀態的變化。C-State取值見表2-1。
- “CPU N Clock Frequency”所示行展示了某個CPU執行的頻率。通過點選這一行的色塊可以檢視某個時間點上CPU N的執行頻率。
- “cpufreq”:該行所示內容和CPU互動式頻率調節器(Interactive Governor)的工作有關。互動式CPU調節器驅動新增了對CPU頻率調節事件的跟蹤。感興趣的讀者不妨閱讀kernel中的include/trace/events/cpufreq_interactive.h檔案以瞭解更多的資訊。
圖2-1中,CPU資訊以下的行就是通過Trace.h提供的巨集而新增的統計資訊,其中:
- VSYNC:這一行的統計資訊來自於圖2-1中ATRACE_INIT巨集的使用。在Hwcomposer程式碼中,ATRACE_INIT巨集被用於統計VSYNC[1]的Tick-Tack情況(即0,1,0,1交錯輸出)。VSYNC行顯示了每次Tick Tack的時間大概都在16ms左右。
- 由於Framework程式碼也在顯示部分新增了ATRACE_INIT的使用,所以圖中com.example.systracedemo/com.example.systracedemo.MainActivity所示為應用程式佔用顯示Buffer的Tick-Tack情況。如果使用時間超過16ms,將導致介面顯示遲滯等現象。
- SurfaceFlinger使用了ATRACE_CALL巨集,故圖中SurfaceFlinger行展示了其函式呼叫的CPU耗時情況(如箭頭1所指,SurfaceFlinger中的onMessageReceived函式的執行資訊)。
- 在圖2-1最下部的方框中,詳細顯示了當前滑鼠在時間線中選擇的部分(即SurfaceFlinger中的onMessageReceived)的詳細資訊。
表2-1所示為CPU狀態取值資訊:
表2-1 CPU狀態
C-state |
描述 |
C-0 |
RUN MODE,執行模式。 |
C-1 |
STANDBY,就位模式,隨時準備投入執行 |
C-2 |
DORMANT,休眠狀態,被喚醒投入執行時有一定的延遲 |
C-3 |
SHUTDOWN,關閉狀態,需要有較長的延遲才能進入執行狀態,減少耗電 |
2.3 Systrace小結
總體來說,Systrace比Traceview用途更廣泛,它支援對CPU、Native程式甚至Kernel執行緒進行效能資料取樣,可幫助開發者對整個系統的效能情況進行一個詳盡的分析。不過其用法比Traceview要複雜,而且還需要對Kernel做一些配置調整。
Android官方對Systrace也有一些介紹,請讀者閱讀:
http://developer.android.com/tools/debugging/systrace.html
三Oprofile的使用
3.1 Oprofile簡介
Oprofile是另一個功能更強大的效能資料採集和分析工具,其工作原理如下:
- 它利用效能計數器(Performance Counter)或者定時器(針對kernel不支援效能計數器的情況),通過連續的取樣獲得統計資料,從而對核心和使用者空間程式進行效能分析。
- 以效能計數器為例,在系統執行過程中,當某個事件發生時,對應的效能計數器就會自加。當達到計數器的設定值時會產生一箇中斷。Oprofile驅動利用這個中斷來進行取樣統計。通過獲取中斷髮生時PC指標的值以及核心中儲存執行的任務的資訊等,並把它們轉化成對測評有用的資料。
- Oprofile包括核心驅動和使用者空間工具兩個部分,其中:
- 核心驅動實現了一個oprofilefs虛擬檔案系統。它掛載到/dev/oprofile,用來向使用者空間報告資料和接收來自使用者空間的設定。它是使用者空間程式與核心通訊的橋樑。驅動中還包括了與架構相關和通用的驅動,通過它們訪問效能計數器暫存器、收集資料後報告給使用者空間。守護程式使用者從核心接收資料並儲存在磁碟上以備分析使用。
- 在使用者空間提供了兩個工具:oprofiled(作為守護程式在後臺通過和/dev/oprofile互動以獲取驅動收集的資料)、opcontrol(使用者操作的控制工具,它通過讀寫oprofilefs來控制取樣相關的設定)。
Android預設提供了對Oprofile的支援,其組成包括:
- 程式碼:位於exetrnal/oprofile中。不過,只有編譯型別為非user的系統才會使用它。
- 四個主要工具,即opcontrol,oprofiled、opreport和opimport。開發者只要使用opcontrol和opreport即可。
- 讀者應該熟練掌握opcontrol和oprofiled工具的作用,我們此處也總結了它們的用法:
- opcontrol:它用來控制取樣過程,比如取樣的開始和結束、取樣的事件型別和頻率等。其內部通過讀寫oprofilefs來實現。opcontrol的常用選項如表3-1所示:
表3-1 opcontrol常用選項
opcontrol選項 |
功能 |
--list-events |
列出當前CPU所支援的事件 |
--setup |
對測評進行設定,比如關閉舊的守護程式、掛載oprofilefs |
--vmlinux= |
設定將要分析的Android核心映象檔案 |
--callgraph |
設定跟蹤函式呼叫的層數 |
--kernel-range=start,end |
核心二進位制檔案起始和結束的虛擬地址 |
--start/--stop |
開始/停止取樣 |
--event=name:count:unitmask:kernel:user |
設定對某事件進行取樣。 Name:事件的名字 Count:取樣時事件發生的次數Unitmask:事件的掩碼(CPU支援的事件以及掩碼見oprofile的文件) Kernel:是否取樣核心事件 User:是否取樣使用者事件 |
- opreport:opreport是使用取樣資料生成報告的工具,可根據使用者要求生成不同的報告。一般用法是“opreport [options] [image]”,其中image指定報告需要顯示的程式的名字(指程式名字、共享庫名字和核心)。image引數可選。不指定它時,opreport將列印所有程式的報告結果。常用options如表3-2所示:
表3-2 opreport常用選項
opreprt選項 |
功能 |
-l |
顯示函式呼叫的符號名字 |
-g |
以除錯的形式列印函式符號,包括函式所在檔案及行數等。 |
-c |
顯示函式呼叫堆疊 |
-o |
報告輸出到指定檔案 |
另外,Android提供了一個特別的工具opimport_pull。它可把取樣資料從手機中pull到PC上,並對資料進行一些簡單處理以供opreport使用。所以,在Android平臺上,開發者只要使用opimport_pull了就可以了。
現在,我們來看Oprofile的使用例項。
3.2 Oprofile例項
Oprofile的使用大體可以分成以下三步:
- 核心載入oprofile驅動(如果該驅動靜態編譯到核心中,則可略過此步驟)。
- 配置取樣事件、然後進行取樣。
- 獲取報告、進行分析,針對分析結果進行改進。
下面分別來看這三個步驟:
3.2.1 Oprofile核心配置
如下所示為核心配置的示例,如圖3-1所示:
圖3-1 Oprofile核心配置示意
執行Oprofile需要root許可權,所以目標裝置中最好執行的是userdebug或者engineer版本的Android OS。
3.2.2 Oprofile使用者空間配置
Oprofile使用者空間配置的示例如圖3-2所示。假設當前目錄為Android原始碼根目錄,並且已經初始化Android編譯環境(執行完畢build/envsetup.sh和lunch)。
圖3-2 Oprofile使用者空間配置示意
使用者空間的配置主要通過執行opcontrol命令來完成。而opcontrol內部是通過往oprofilefs傳遞對應的控制引數來完成的。例如圖3-2中“opcontrol --callgraph=16”命令也可通過“echo 16> /dev/oprofile/backtrace_depth”來實現。
3.2.3 結果分析
在上一步中,我們已經獲取了測評取樣的資料。現在,就可以使用它們來生成取樣報告了,方法如圖3-3所示:
圖3-3 oprofile生成取樣報告方法示意
圖3-4為報告的一部分內容:
圖3-4 Oprofile測評報告概要
圖3-4中,我們發現libc.so呼叫的取樣數為117299,排第4位。那麼libc.so中哪個函式呼叫次數最多呢?開發者可通過如下命令獲取libc.so的更為詳細的資訊。方法如圖3-5所示:
圖3-5 opreport使用示例
執行上述命令後的結果如圖3-6所示:
圖3-6 Oprofile關於libc的詳細結果
由圖3-6可知,memcpy()函式佔用最多的CPU資源。所以可以考慮優化memcpy()。
此處,筆者針對Cortex-A9雙核SMP處理器,使用ARM彙編的方法對memcpy進行了優化。優化後的結果如圖3-7所示。對比圖3-6和圖3-7可明顯看出,優化後的memcpy對資源的佔用降低了2.7個百分點。
圖3-7 優化memcpy()後的測試結果
3.3 Oprofile小結
在效能分析中,Oprofile無疑是一個使用最廣泛、功能最強大的測評工具。對於Android平臺開發者來說,它可以採集和分析整個系統的執行狀態資訊,對於分析查詢系統瓶頸進而優化系統具有重大意義。
四 總結
效能調優向來是一件“高深莫測”的任務,但打破它們神祕面紗的工具就是上文所述的工具了。所以,對有志開展這方面工作的讀者而言,首要一步的工作就是先了解各個工具的作用及優缺點。
除了本文介紹的這三個工具外,Android系統還支援其他一些更有針對性的測試工具,例如用於測評系統整體功能的lmbench,lttng、測試系統啟動效能的bootchart、測試檔案系統效能的iozone等。由於篇幅關係,筆者就不再一一介紹它們了。
[1]關於VSYNC的詳情,讀者可參考http://blog.csdn.net/innost/article/details/8272867,“Android Project Butter分析“一文。