3-14.【OC】【Runtime】cache miss 一定会去遍历方法列表吗?还有哪些优化路径?

0 阅读3分钟

如果每次 Cache Miss(缓存未命中)都去老老实实地遍历几千个方法的列表,Objective-C 的性能早就崩了。

在进入最慢的“方法列表遍历”之前,Runtime 实际上设计了好几层“减速带”和“快捷通道”。


1. 第一道防线:空指针与 Tagged Pointer

在查找 Cache 之前,objc_msgSend 的汇编代码会先进行一次极速预检

  • Nil Check:如果 receivernil,直接清理寄存器并退出(这就是为什么在 OC 里给 nil 发消息不崩溃)。
  • Tagged Pointer:如果是 NSNumberNSDate 等小对象,它们的类信息不在普通的 isa 里,而是在指针本身。Runtime 会直接跳转到专门为这些小对象优化的查找逻辑,速度极快。

2. 第二道防线:共享缓存 (dyld Shared Cache)

这是 2026 年甚至更早版本 iOS 系统性能的核心秘密。

对于像 NSStringNSArray 这种系统级的类,它们的方法实在是太常用了。Apple 在系统镜像打包时,会将这些类的方法信息预先处理好,放在 dyld 共享缓存 中。

  • 优化逻辑:当你的 CustomString 继承自 NSString 时,如果你调用一个 NSString 的方法,即使子类缓存没中,Runtime 有可能直接去共享区域读取,而不需要去扫描内存中的类结构。

3. 第三道防线:查找时的“二分搜索” (Binary Search)

即便真的需要去方法列表里找,Runtime 也不是每次都从头往后数。

  • 排序列表:在编译阶段,类的方法列表通常是按 Selector 的地址排好序的
  • 算法优化lookUpImpOrForward 在查找时,如果检测到方法列表是有序的,会直接执行 二分查找。对于拥有几百个方法的类,这能将复杂度从 O(n)O(n) 降低到 O(logn)O(\log n)

4. 2026 年新特性:Small Method List 优化

在近年的 Runtime 更新中,Apple 引入了 Small Method 概念。

  • 原理:以前的方法列表存储的是 64 位的绝对指针(很占空间且读取慢)。现在的“Small Method”使用 32 位相对偏移量
  • 好处:这不仅减小了二进制文件的体积,还显著提高了 CPU 缓存命中率(L1/L2 Cache),因为更多的方法定义被挤在同一个内存页里,CPU 读取一次内存能拿到更多方法信息。

5. 最终路径:消息转发的缓存

如果查完了方法列表还是没找到,系统会进入动态决议resolveInstanceMethod:)。

有趣的是,动态决议的结果也会被缓存。一旦你动态添加了方法,下次调用就会直接命中 cache_t,永远不会再走一遍“Miss -> 遍历 -> 决议”的老路。


总结:查找路径的优先级

阶段路径名称性能开销核心逻辑
0Pre-flight极低处理 nil 和 Tagged Pointer
1Fast Path极低objc_msgSend 汇编查 cache_t
2Shared Pathdyld 共享缓存命中
3Medium Path有序列表的 二分查找
4Slow Path无序列表的 线性遍历
5Dynamic Path极高触发动态补救和消息转发

一个硬核的小细节

在移动端,内存读取(I/O)往往比 CPU 计算更贵。所以 Runtime 的逻辑是:宁愿多花几个 CPU 周期做位运算和二分查找,也要极力避免去翻动那些还没加载进内存页的方法列表。