【背景介绍】
虽然iPhone的性能越来越好,但app的功能也越来越复杂,性能从来都是移动开发的核心关注点之一。我们说一个app性能好,不是简单指感觉运行速度快,而应该是指应用启动快速、UI反馈响应及时、列表滚动操作流畅、内存使用合理,当然更不能出现简单的crash了。
那么iOS的性能测试是什么:资源消耗、内存泄漏、流量消耗、耗电功率、渲染效果、加载时间。。。
以下将结合iPhone浏览器从启动时间、加载时间、内存占用、CPU和流畅度等维度介绍如何完成一个iOS app的性能测试。其中会用到Apple的性能分析神器”Instruments”。
一、启动时间
移动应用的启动时间是用户体验的一个重要方面,苹果一直建议尽可能的缩短启动时间,防止用户不愿意使用它们。对于浏览器而言,由于程序启动时还会有教育页和闪屏的下发,因此启动时间的获取显得尤为重要。
启动时间分为冷启动时间和热启动时间,所谓的“冷启动”,就是一个完全没有运行的应用的启动时间,与热启动(应用已经在后台运行,某个事件将其带至前台)相比,由于此时系统尚未建立缓存,因此冷启动往往要较平时(热启动)耗费更长的时间。
要获取准确的app启动所需时间,最简单的就是通过性能打点的方法。首先在main.c中添加如下代码:
CFTimeInterval startTimeLog;
int main(int argc, char *argv[]) {
startTimeLog = CACurrentMediaTime();
然后在AppDelegate的回调方法application:didFinishLaunchingWithOptions中添加:
dispatch_async(dispatch_get_main_queue(), ^{
CGFloat launchTime = CACurrentMediaTime() - startTimeLog;
NSLog(@"launch: %f", launchTime);
});
可能你会疑问为什么这样可以获得系统启动的时间,因为这个dispatch_async中提交的工作会在app主线程启动后的下一个runloop中运行,此时app已经完成了载入并且将要显示第一帧画面,也就是系统会运行到-[UIApplication _reportAppLaunchFinished]之前。
下图是用Instruments工具Time Profiler跑的调用栈信息。
图1
所以使用Time Profiler同样可以查看app的启动时间,具体方法如下:
-
1. Instruments->Time Profiler
-
2. Profiler你的app
-
3. 切换到CPU strategy view,找到你的app启动的第一帧
-
4. 搜索-[UIApplication _reportAppLaunchFinished]的最后一帧,即可算出启动时间(图中为_reportMainScreenUpdateFinished:)
为了拿到真实的用户数据,追踪版本之间的数据变化,目前浏览器线上版本中启动时间已作为性能埋点上传,这样我们就可以计算出每日不同机型不同OS的平均启动时间,以帮助更加实时有效的监控线上的性能质量数据。
二、网页加载时间
据Google Analytics数据,目前移动网页平均加载时间至少需要7秒,Google的目标是把这个时间降至1秒,因为参考Nielsan Norman Group 的调查研究结果:如果移动网页加载时间超过 1 秒,用户就不愿停留在页面上了。
在目前的技术基础上,在几百毫秒内加载数个网页几乎是不可能实现的,但在1秒内完成移动网页首页内容加载是有可能的,而剩余内容则慢慢加载。因此网页加载首屏展现时间成为了衡量手机浏览器的一个重要性能指标,计算方法为从开始加载网页到首屏内容全部展现所用的时间。
网页加载时间同样可以基于打点的方法获得。移动网页的加载都是从Webview的url请求开始的,webview的操作都会有UIWebviewDelegate的方法代理完成,因此通过对webview代理方法的研究(见下图),选取正确的方法作为开始时间和结束时间,即可获取网页的首屏加载展现时间和网页加载完成时间。
图2
对于竞品,我们则可以在越狱机型上通过动态库hook的方法进行加载时间的计算,简单介绍如下:
-
1. class-dump-z命令获得应用程序的类信息
-
a) 导出设备上预装应用的类信息(/Applications)
-
b) 导出从AppStore下载的app的类信息,需要clutch命令破解(/var/mobile/Applications)
-
2. 用GDB或Cycript进行运行时分析(Cycript是一个理解Objective-C语法的javascript解释器),hook当前运行的进程,打印当前运行的viewcontroller及对应的方法名;
-
3. 动态库注入,执行method swizzling,在对应的方法中打印时间日志
关于如何使用运行时分析及动态库注入,这里就不详细说明了。
当然,加载时间的查看也可以借助于Apple的性能分析神器Instruments。当我们发现App有点卡的时候,就可以通过Time Profiler来查看耗时在哪里,如图突出的范围就是步骤消耗的时间。
图3
在这里同时分享一个基于摄像+分析的快速进行启动时间和加载时间计算的方法。当前手机的摄像头基本上都支持高FPS的拍摄,拍下来的MP4文件可以通过免费的Avidemux工具来看具体的帧信息,也可以看到帧的时间戳,根据拍摄的格式,目前我测试的视频可以达到30毫秒级别,完全满足性能测试的需求。
三、内存测试
iOS系统中内存限制是较为严格的,因此内存优化也就成了iOS app一直以来的难题。关于内存测试的方法有很多,可以直接用Xcode真机Debug查看,也可以通过Instruments中内存相关的模板Activity Monitor获得。
图4 Xcode调试查看内存
图5 Activity Monitor查看内存
在实际性能测试中,内存测试往往会分场景进行,通过脚本模拟用户常用场景操作,分析该场景下的内存占用情况。
1. 指定run.js脚本测试
$ instruments -w ${UDID} -t ${template} ${APP} -e UIASCRIPT ${script} > .input.log
2. 解析ActivityMonitor模板的trace文件,生成对应的json格式数据
$ instruments_parser -p process_name -i result.trace
其中一个json块数据格式参照如下:
{
"Threads" : 12,
"UnixSyscalls" : 14314,
"Command" : "com.baidu.ime.Ba",
"VirtualSize" : 718213120,
"ContextSwitches" : 5774,
"Ports" : 166,
"PageIns" : 4881,
"Shared" : 12976128,
"PPID" : 1,
"CPUUsage" : 0,
"UID" : 501,
"TotalMicroSeconds" : 307788,
"Timestamp" : 1421818303.125269,
"VPrivate" : 29028352,
"Date" : "2015-01-21 13:31:43:125",
"MessagesSent" : 4042,
"PID" : 721,
"TotalSeconds" : 1,
"Private" : 9338880,
"PGID" : 721,
"MachSyscalls" : 7186,
"ResidentSize" : 39362560,
"Architecture" : 16777228,
"Faults" : 19274,
"MessagesReceived" : 1709
}
3. 统计ResidentSize、VirtualSize字段,使用python的matplotlib图形库生成内存变化图表。
然而对于内存测试,如果你觉得只是需要跟踪app的内存使用情况,那么你就错了。一套完整的内存管理测试方案需要关注的点其实还有很多,比如使用Leaks分析内存泄漏,使用Allocations分析内存浪费,使用Zombie分析野指针,使用VMTracker测试虚拟内存,代码中是否仍使用ARC机制等等。
其中关于虚拟内存的测试或许是最容易被忽略的,浏览器就曾经发现过实际内存占用不高,但虚拟内存上涨很快,从而导致app因为内存不足被系统kill的问题。
那么如何分析app的虚拟内存呢?我们可以通过Instruments的VM Tracker进行查看。VM Tracker主要用于记录app的虚拟内存分配,该模板会显示app中分配了多大的虚拟内存空间,其中多少是Dirty的内存,有多少是被映射到实际物理内存中,并且可以显示详细的虚拟内存分配情况。
图6 VM Tracker查看虚拟内存
关于上图中的dirty size,这里介绍一下dirty & clean的概念。
在程序使用的内存page中,iOS区分两种内存,一种为clean,一种为dirty。
clean page的概念为所有可以被废弃并且重新生成的page,例如二进制代码等从磁盘读取的文件,例如未曾读写过的page,或者被标识为可擦除的内存等。
dirty page的概念为无法重新生成的page,即app生成的,并且已经写入过的page,例如使用malloc分配的heap内存,全局变量,stack内存等。
当系统发现可用内存较少时,会将resident中的clean page进行清除,当有需要使用时直接从磁盘读取就行。系统不能卸载掉dirty memory,因为iOS是没有内存置换机制的。当dirty memory达到一个上限时,应用会被kill,由系统回收内存。
说到上限,这里可能有人会问,在iOS设备中打开很多app后,打开被测app,该app占用内存的上限能达到多少呢?我们可以通过demo app,手动malloc内存,也可以通过instruments查看,观察内存警告时,App被kill时的日志输出。
下表列出了对各种设备进行测试后得到的数值,供大家参考。
图7 不同设备内存占用限制
四、CPU测试
CPU测试的方法和内存较为类似,可以通过Instruments中的Activity Monitor模板查看,也可以通过客户端打点的方法获取。
在浏览器性能测试中,重点模块的CPU测试还需要针对不同机型不同Architecture指令集进行兼容。例如在iPhone浏览器播放内核库的测试中就需要兼容armv7、armv7s、arm64、i386、x86-64五种CPU上都经过测试。
五、流畅度
对于浏览器而言,会存在着较多网页浏览、动画显示等操作,这时是否存在卡顿对于用户体验就显得较为重要。关于流畅度的测试我们可以通过使用instruments的core animation工具,浏览网页或加载动画,查看fps的帧数。一般而言,当用户操作时,如果fps帧数小于40,则说明存在卡顿的情形。
图8 Core Animation查看fps帧数
百度将于近期推出技术类图书《如何高效地开发一款高质量的移动APP》(名称待定),这将是百度首次技术输出,选题聚焦在移动互联网领域,内容覆盖APP开发、部署、测试、分发、变现、监控和数据分析的全过程。帮助移动APP开发者,更好的了解百度的领先技术、项目经验以及自主研发工具等。
作为业界领先的移动应用一站式测试服务平台,百度MTC覆盖移动应用从开发、测试到上线、运营的整个生命周期,为广大开发者在移动应用开发测试过程中面临的成本、技术和效率问题提供解决方案。本次出书稿件将陆续在MTC学院发表(http://mtc.baidu.com/academy/article),同步覆盖其他技术论坛,并将在今年上半年集结成册,正式出版发行,敬请期待吧!