Skip to content

Latest commit

 

History

History
65 lines (52 loc) · 4.5 KB

performance.md

File metadata and controls

65 lines (52 loc) · 4.5 KB

iOS 和 macOS 性能优化

命令行工具

top

top 命令能够连续动态地更新显示大量有关系统性能的参数。top -u 能够把 CPU 当前的占用比排序,置顶最活跃的进程。

time

使用 time 命令测试程序的耗时。

simple

查看程序的调用树。可以集合 grep 来过滤掉一些干扰数据。

getrusage()

调用该方法与使用 toptime 命令获取的信息相同,但没有启动单独进程的开销。

Instruments

macOS 冷启动性能分析

当需要测试应用程序「冷启动」性能时使用它进行测试非常有效。冷启动是指启动和登录后的过程,此时 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() 函数执行开始,到 appDelegatedidFinishLaunchingWithOptions 方法里首屏渲染相关方法执行完成。
      • 首屏初始化所需配置文件的读写操作;
      • 首屏列表大数据的读取;
      • 首屏渲染的大量计算。
    • 首屏渲染完成后。
  • 对 App 启动速度的监控:
    • 定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。Xcode 的 Time Profiler 就是这种方式。一般时间设置在 0.01 秒。
    • objc_msgSend 方法进行 hook
      • OC 方法可以使用 objc_msgSend 方法进行 hook,对于 C 函数和 block 可以使用 libffi里的 ffi_call 完成。
      • objc_msgSend 本身使用汇编写的。
        • 因为调用频次最高,汇编在性能上属于原子级优化。
        • 使用其它语言难以做到未知参数跳转任意函数指针的功能。
        • 其自身的执行逻辑为:先获取对象对应类的信息,再获取方法的缓存,根据方法的 selector 查找函数指针,经过异常错误处理后,最后跳到对应函数的实现。