一克胳、什么是 App性能?性能指標是什么圈匆?
一般來講漠另,性能問題雖然不會導致 App不可用,但會影響用戶體驗臭脓,用戶只關心產(chǎn)品體驗問題,如果說性能問題不斷累計達到臨界點腹忽,就會爆發(fā)出一些不可估計的問題来累,要么任性地卡頓運行砚作,要么就低調地崩潰,最后導致用戶用著不開心嘹锁,開發(fā)者也比較煩惱葫录,如果是這樣的話,你在這家公司的可能就呆不長時間了领猾。
性能指標:主要集中在 CPU 使用率米同、FPS 的幀率和內存這三個方面
二、為什么需要考慮 App性能呢摔竿?
做任何一件事情面粮,我們都必須追求完美,做開發(fā)也是一樣的继低,我們都需要站在用戶角度和公司利益出發(fā)去考慮問題熬苍,去工作,去打造一款在市場上能站穩(wěn)腳的 App袁翁。一款好的 App柴底,不僅僅是要抓住了用戶需求,還需要完美的體驗粱胜,視覺的沖擊柄驻,得讓用戶用起來舒服,有種愛不釋手的感覺焙压,“阿哈”的感覺鸿脓。(“阿哈”解釋于《黑客增量》一書中)
把自己往大牛的路上逼,我要當大牛
三冗恨、如何對 App做性能監(jiān)測答憔?
為了主動、高效地發(fā)現(xiàn)性能問題掀抹,避免 App 質量進入無人監(jiān)管的失控狀態(tài)虐拓,我們需要對App 的性能進行監(jiān)控,目前傲武,對 App 的性能監(jiān)控蓉驹,主要是分為線下和線上兩個方面。
線下性能監(jiān)控:Instruments
Instruments是由蘋果官方力推的唯一一款線下性能監(jiān)控揪利,性能分析工具态兴,集成于xcode,功能非常強大疟位,比如說Leaks專門用于監(jiān)控內存泄露問題的瞻润,Network專門用于檢查網(wǎng)絡情況的,Time Profiler對程序命令執(zhí)行時間的采樣來分析頁面卡頓問題的,等等......
Instruments 可以實現(xiàn)下面功能:
1.分析一個或多個進程的行為绍撞。
2.記錄一系列用戶的動作并響應它們正勒,可靠的再現(xiàn)這些事件并收集多次運行的數(shù)據(jù)。
3.創(chuàng)建你自己自定義的 DTrace instruments 來分析系統(tǒng)和應用程序的行為傻铣。
4.保存用戶界面記錄和instruments的配置為模板章贞,并從Xcode里面訪問。
5.追查代碼中難以重現(xiàn)的問題非洲。
6.對你的程序進行性能分析鸭限。
7.自動化測試你的代碼。
8.對你程序進行壓力測試两踏。
9.進行一般的系統(tǒng)級故障診斷败京。
10.對你的代碼如何工作有更深入的了解。-
啟動Instruments:
image.png
啟動后的界面:
啟動后的界面.png Instruments創(chuàng)建跟蹤文檔:
1.Blank:創(chuàng)建一個空的模板缆瓣,你可以自定義的添加各種工具喧枷。
2.Activity Monitor:可以只用這個模板,研究系統(tǒng)工作負載和虛擬內存大小的關系
3.Allocations:將Allocations和VM跟蹤器加到跟蹤文檔中弓坞,使用該工具可以監(jiān)視內存和對象的內存分配方式和情況隧甚。
4.CocoaLayout:是一種Cocoa布局工具 ,可以應用于iOS模擬器和Cocoa桌面應用渡冻,但是不能和連接的iOS設備一起使用戚扳。觀察NSLayoutConstraint對象的改變,幫助我們判斷什么時間什么地點的constraint是否合理
5.Core Animation:將CoreAnimation加入到跟蹤文檔中族吻,可以測量ios設備上每秒的CoreAnimation幀數(shù)帽借,這可以幫助你理解內容是如何渲染到屏幕上的
6.CoreData:將CoreData數(shù)據(jù)提取、緩存缺失和存儲加入到跟蹤文檔中超歌,使用這個工具可以檢測應用程序中數(shù)據(jù)的存儲交互砍艾。
7.Counters:收集使用時間或基于事件的抽樣方法的性能監(jiān)控計數(shù)器(PMC)事件。
8.Energy Log:耗電量監(jiān)控巍举,將Energy Diagnostics, CPU Activity, Display Brightness, Sleep/Wake, Bluetooth, WiFi, and GPS instruments加入到跟蹤文檔中進行檢測脆荷。
9.File Activity:將File Activity, Reads/Writes, File Attributes, and Directory I/O instruments 加入到跟蹤文檔中,只用這個模板可以讓你檢查系統(tǒng)文件的使用情況懊悯,可以檢查文件的打開蜓谋、關閉、讀和寫操作炭分,同時也可以檢測文件系統(tǒng)本身的改變桃焕,包括權限和所有權發(fā)生的改變。
10.Leaks:將the Allocations and Leaks instruments加入到模板中捧毛,使用這個模板可以幫助你檢測內存的泄漏观堂。
11.Metal System Trace:它是是apple 2014年在ios平臺上推出的高效底層的3D圖形API让网,它通過減少驅動層的API調用CPU的消耗提高渲染效率。
12.Network:用鏈接工具分析你的程序如何使用TCP/IP和UDP/IP鏈接师痕。
13.OpenGL ES Analysis:將OpenGL ES Analyzer and OpenGL ES Driver加入到模板中寂祥,這個模塊測量分析OpenGL ES活動正確性檢測以及表現(xiàn)問題,提供解決建議七兜。
14.System Trace:系統(tǒng)跟蹤,通過顯示當前被調度線程提供綜合的系統(tǒng)表現(xiàn)福扬,顯示從用戶到系統(tǒng)的轉換代碼通過兩個系統(tǒng)調用或內存操作腕铸。
15.System Usage:這個模板記錄關于文件讀寫,sockets铛碑,I/O系統(tǒng)活動狠裹, 輸入輸出。
16.Time Profile:執(zhí)行對系統(tǒng)的CPU上運行的進程低負載時間為基礎采樣汽烦。
17.Zombies:測量一般的內存使用涛菠,專注于檢測過度釋放的野指針對象,也提供對象分配統(tǒng)計撇吞,以及主動分配的內存地址歷史俗冻。-
跟蹤文檔舉例:如Leaks
Leaks
Leaks圖中數(shù)字說明說明:
1.開始錄制/暫停
2.call tree的官方解釋:Separate By Thread(線程分離,只有這樣才能在調用路徑中能夠清晰看到占用CPU最大的線程.)
3.Invert Call Tree 的意思是翻轉調用樹,直接定位到內存泄漏的那個方法牍颈,如果不勾選會顯示最外層的函數(shù)迄薄,我們還要手動展開≈笏辏可以點選看下效果
Hide System Libraries 的意思很明顯了讥蔽。就是隱藏系統(tǒng)類庫,避免一些莫名其妙的画机,我們無法改動的信息迷惑我們冶伞。
4.刷新間隔時間
5.雙擊第三行定位到具體泄漏的函數(shù),泄漏的字節(jié)數(shù)都標出來了步氏,如此強大响禽,定位到泄漏代碼,加以更改后就可以了
如下圖舉例:
image.png -
功能簡介:
Instruments功能簡介圖
其中常用的有以下幾種工具:
Allocations戳护,
Leaks金抡,
Zombies,
Core Animation腌且,
Automation梗肝,
Time Profiler,
Cocoa Layout铺董,
Energy Diagnostics巫击,
Network
- 自定義Instruments
除了對各種性能問題進行監(jiān)控外禀晓,最新版本的 Instruments 10 還有以下兩大優(yōu)勢:
1.Instruments 基于 os_signpost 架構,可以支持所有平臺坝锰。
2.Instruments 由于標準界面(Standard UI)和分析核心(Analysis Core)技術粹懒,使得我們可以非常方便地進行自定義性能監(jiān)測工具的開發(fā)。當你想要給 Instruments 內置的工具換個交互界面顷级,或者新創(chuàng)建一個工具的時候凫乖,都可以通過自定義工具這個功能來實現(xiàn)。
開發(fā)一款自定義 Instruments 工具弓颈,主要包括以下這幾個步驟:
a.在 Xcode 中帽芽,點擊 File > New > Project;
b.在彈出的 Project 模板選擇界面翔冀,將其設置為 macOS导街;
c.選擇 Instruments Package,點擊后即可開始自定義工具的開發(fā)了
點擊查看詳情
線上性能監(jiān)控:CPU 使用率纤子、FPS 的幀率和內存
需要考慮兩個原則:
a.監(jiān)控代碼不要侵入到業(yè)務代碼中搬瑰。
b.采用性能消耗最小的監(jiān)控方案。
CPU 使用率的線上監(jiān)控方法
App 作為進程運行起來后會有多個線程控硼,每個線程對 CPU 的使用率不同泽论。各個線程對 CPU 使用率的總和,就是當前 App 對 CPU 的使用率卡乾。明白了這一點以后佩厚,我們也就摸清楚了對 CPU 使用率進行線上監(jiān)控的思路。
在 iOS 系統(tǒng)中说订,你可以在 usr/include/mach/thread_info.h 里看到線程基本信息的結構體抄瓦,其中的 cpu_usage 就是 CPU 使用率。結構體的完整代碼如下所示:
struct thread_basic_info {
time_value_t user_time; // 用戶運行時長
time_value_t system_time; // 系統(tǒng)運行時長
integer_t cpu_usage; // CPU 使用率
policy_t policy; // 調度策略
integer_t run_state; // 運行狀態(tài)
integer_t flags; // 各種標記
integer_t suspend_count; // 暫停線程的計數(shù)
integer_t sleep_time; // 休眠的時間
};
因為每個線程都會有這個 thread_basic_info 結構體陶冷,所以接下來的事情就好辦了钙姊,你只需要定時(比如,將定時間隔設置為 2s)去遍歷每個線程埂伦,累加每個線程的 cpu_usage 字段的值煞额,就能夠得到當前 App 所在進程的 CPU 使用率了。實現(xiàn)代碼如下:
+ (integer_t)cpuUsage {
thread_act_array_t threads; //int 組成的數(shù)組比如 thread[1] = 5635
mach_msg_type_number_t threadCount = 0; //mach_msg_type_number_t 是 int 類型
const task_t thisTask = mach_task_self();
// 根據(jù)當前 task 獲取所有線程
kern_return_t kr = task_threads(thisTask, &threads, &threadCount);
if (kr != KERN_SUCCESS) {
return 0;
}
integer_t cpuUsage = 0;
// 遍歷所有線程
for (int i = 0; i < threadCount; i++) {
thread_info_data_t threadInfo;
thread_basic_info_t threadBaseInfo;
mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;
if (thread_info((thread_act_t)threads[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount) == KERN_SUCCESS) {
// 獲取 CPU 使用率
threadBaseInfo = (thread_basic_info_t)threadInfo;
if (!(threadBaseInfo->flags & TH_FLAGS_IDLE)) {
cpuUsage += threadBaseInfo->cpu_usage;
}
}
}
assert(vm_deallocate(mach_task_self(), (vm_address_t)threads, threadCount * sizeof(thread_t)) == KERN_SUCCESS);
return cpuUsage;
}
在上面這段代碼中沾谜,task_threads 方法能夠取到當前進程中的線程總數(shù) threadCount 和所有線程的數(shù)組 threads膊毁。
接下來,我們就可以通過遍歷這個數(shù)組來獲取單個線程的基本信息基跑。其中婚温,線程基本信息的結構體是 thread_basic_info_t,這個結構體里就包含了我們需要的 CPU 使用率的字段 cpu_usage媳否。然后栅螟,我們累加這個字段就能夠獲取到當前的整體 CPU 使用率荆秦。
到此,我們就實現(xiàn)了對 CPU 使用率的線上監(jiān)控力图。接下來步绸,我們再看看對 FPS 的線上監(jiān)控方法吧。
FPS 線上監(jiān)控方法
FPS 是指圖像連續(xù)在顯示設備上出現(xiàn)的頻率吃媒。FPS 低瓤介,表示 App 不夠流暢,還需要進行優(yōu)化赘那。
但是惑朦,和前面對 CPU 使用率和內存使用量的監(jiān)控不同,iOS 系統(tǒng)中沒有一個專門的結構體漓概,用來記錄與 FPS 相關的數(shù)據(jù)。但是病梢,對 FPS 的監(jiān)控也可以比較簡單的實現(xiàn):通過注冊 CADisplayLink 得到屏幕的同步刷新率胃珍,記錄每次刷新時間,然后就可以得到 FPS蜓陌。具體的實現(xiàn)代碼如下:
- (void)start {
self.dLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(fpsCount:)];
[self.dLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
// 方法執(zhí)行幀率和屏幕刷新率保持一致
- (void)fpsCount:(CADisplayLink *)displayLink {
if (lastTimeStamp == 0) {
lastTimeStamp = self.dLink.timestamp;
} else {
total++;
// 開始渲染時間與上次渲染時間差值
NSTimeInterval useTime = self.dLink.timestamp - lastTimeStamp;
if (useTime < 1) return;
lastTimeStamp = self.dLink.timestamp;
// fps 計算
fps = total / useTime;
total = 0;
}
}
內存使用量的線上監(jiān)控方法
通常情況下觅彰,我們在獲取 iOS 應用內存使用量時,都是使用 task_basic_info 里的 resident_size 字段信息钮热。但是填抬,我們發(fā)現(xiàn)這樣獲得的內存使用量和 Instruments 里看到的相差很大。后來隧期,在 2018 WWDC Session 416 iOS Memory Deep Dive中飒责,蘋果公司介紹說 phys_footprint 才是實際使用的物理內存。
內存信息存在 task_info.h (完整路徑 usr/include/mach/task.info.h)文件的 task_vm_info 結構體中仆潮,其中 phys_footprint 就是物理內存的使用宏蛉,而不是駐留內存 resident_size。結構體里和內存相關的代碼如下:
struct task_vm_info {
mach_vm_size_t virtual_size; // 虛擬內存大小
integer_t region_count; // 內存區(qū)域的數(shù)量
integer_t page_size;
mach_vm_size_t resident_size; // 駐留內存大小
mach_vm_size_t resident_size_peak; // 駐留內存峰值
/* added for rev1 */
mach_vm_size_t phys_footprint; // 物理內存
}
OK性置,類似于對 CPU 使用率的監(jiān)控拾并,我們只要從這個結構體里取出 phys_footprint 字段的值,就能夠監(jiān)控到實際物理內存的使用情況了鹏浅。具體實現(xiàn)代碼如下:
uint64_t memoryUsage() {
task_vm_info_data_t vmInfo;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
if (result != KERN_SUCCESS){return 0;}
return vmInfo.phys_footprint;
}
從以上三個線上性能監(jiān)控方案可以看出嗅义,它們的代碼和業(yè)務邏輯是完全解耦的,監(jiān)控時基本都是直接獲取系統(tǒng)本身提供的數(shù)據(jù)隐砸,沒有額外的計算量之碗,因此對 App 本身的性能影響也非常小,滿足了我們要考慮的兩個原則季希。
說明:
本文內從不做任何商業(yè)用途继控,僅僅本人學習筆記記錄械馆,方便以后回顧