top
命令能够连续动态地更新显示大量有关系统性能的参数。top -u
能够把 CPU 当前的占用比排序,置顶最活跃的进程。
使用 time
命令测试程序的耗时。
查看程序的调用树。可以集合 grep
来过滤掉一些干扰数据。
调用该方法与使用 top
和 time
命令获取的信息相同,但没有启动单独进程的开销。
当需要测试应用程序「冷启动」性能时使用它进行测试非常有效。冷启动是指启动和登录后的过程,此时 UI 程序还未驻留内存。但是启动 Instruments GUI 程序会加载大多数 GUI 框架,这意味着使用应用程序时,收集的任何信息不再代表冷启动。这种情况一般出现在 macOS 上。
Instruments 可以允许在不与 UI 交互的情况下启动性能分析会话。例如:
instruments -t "Time Profile" -l 2000 -D sumintsm-cpu.trace.sumintsm
通过启动以上命令启动 Instruments 命令行工具,使用 Time Profile
工具配置 2s,然后将结果数据写入跟踪文档 sumintsm-cpu.trace
,如果文件已经存在,则追加数据。
命令行程序的占用空间比 GUI 应用程序小得多,不加载任何 UI 框架或资源。
指所需要的大部分数据已经存在于缓存之中,而冷启动需要从磁盘中读取所有代码、资源和数据。
进行「内存转储」。只需要找到保存数据的基地址,然后将其写入 write()
或包装在 NSData()
中,以便通过 writeTo...
这类方法执行写入操作,最后记得要使用非复制版本的初始化方法 initWithBytesNoCooy...
。
可以这么做的前提是,需要掌握完整的内部数据结构才能读取和写入对应的文件格式。
- 冷启动:App 在启动之前它的进程不在系统里,需要系统创建一个进程分配给它进行启动。
- 热启动:App 在冷启动后,将 App 退到后台,App 的进程还在系统里,用户重新进入 App 的过程,这个过程做的事情非常少。
- App 启动主要包括三个阶段:
main()
函数执行前;- 加载可执行文件(
.o
文件的集合; - 加载动态链接库,进行
rebase
指针调整和bind
符号绑定; - Objc 运行时的初始处理,包括 Objc 相关类的主持、
category
注册、selector
唯一性检查等; - 初始化。包括执行
+load()
方法、attribute((constructor))
修饰的函数的调用、创建 C++ 静态全局变量。
- 加载可执行文件(
- 在
main()
函数执行前阶段可以做的优化:- 减少动态库加载。Apple 建议使用更少的动态库,并建议多个动态库进行合并,最多一个动态库可以支持 6 个非系统动态库合并。
- 减少加载启动后不会去使用的类或者方法。
+load()
方法里的内容可以放到首屏渲染完成后再执行操作,或者使用+initialize()
方法替换掉。因为在+load()
方法里,进行运行时方法替换会带来 4ms 的消耗。- 控制 C++ 全局变量的数量。为什么会有问题?
main()
函数执行后;- 指的是从
main()
函数执行开始,到appDelegate
的didFinishLaunchingWithOptions
方法里首屏渲染相关方法执行完成。 - 首屏初始化所需配置文件的读写操作;
- 首屏列表大数据的读取;
- 首屏渲染的大量计算。
- 指的是从
- 首屏渲染完成后。
- 对 App 启动速度的监控:
- 定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。Xcode 的 Time Profiler 就是这种方式。一般时间设置在 0.01 秒。
- 对
objc_msgSend
方法进行 hook- OC 方法可以使用
objc_msgSend
方法进行 hook,对于 C 函数和block
可以使用libffi
里的ffi_call
完成。 objc_msgSend
本身使用汇编写的。- 因为调用频次最高,汇编在性能上属于原子级优化。
- 使用其它语言难以做到未知参数跳转任意函数指针的功能。
- 其自身的执行逻辑为:先获取对象对应类的信息,再获取方法的缓存,根据方法的 selector 查找函数指针,经过异常错误处理后,最后跳到对应函数的实现。
- OC 方法可以使用