Skip to content

Latest commit

 

History

History
65 lines (52 loc) · 4.5 KB

File metadata and controls

65 lines (52 loc) · 4.5 KB

iOS 和 macOS 性能优化

命令行工具

top

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

time

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

simple

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

getrusage()

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

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