简单直接的答案是:不,ARC 不仅有性能成本,甚至在某些特定情况下比手动管理(MRC)还要高。
虽然 ARC 的初衷是自动化和安全,但“自动化”的代价是由 CPU 的时钟周期和内存开销来支付的。我们可以从以下四个维度来看 ARC 的“隐藏账单”:
1. 冗余的运行时函数调用
在 MRC 中,你只在必要时写 [obj retain]。但在 ARC 中,为了保证绝对安全,编译器往往采取保守策略。
- 原子性与保护:当你读取一个属性时,ARC 可能会插入
objc_loadWeakRetained或objc_retainAutorelease。 - 状态检查:每一次
objc_storeStrong(最常见的赋值操作)都要先判断新旧对象是否相同,然后执行release旧值、retain新值的逻辑。这些判断和分支跳转在极高频的循环中会累积成明显的开销。
2. 弱引用表(Weak Table)的维护
这是 ARC 性能成本最高的环节。
- 哈希开销:每次创建
__weak指针,系统都要去全局SideTable里通过哈希算法找桶、加锁、插入地址。 - 清理开销:当对象销毁时,系统必须遍历弱引用数组并逐一置空。如果你有一个被上千个弱引用观察的对象,销毁它时会产生明显的 CPU 尖峰。
3. 阻塞编译器优化
- 无法内联:由于
objc_retain和objc_release是动态库(libobjc)里的函数,编译器很难跨模块对这些调用进行内联优化。 - 寄存器压力:ARC 插入的这些函数调用需要占用寄存器来传递参数,这限制了编译器在复杂算法中对寄存器的优化分配。
4. 异常处理的膨胀
为了确保在代码抛出异常(Exception)时对象依然能正确释放,ARC 必须在某些地方生成额外的异常处理表(Exception Handling Tables)。这不仅增加了二进制文件的大小,也给运行时的栈回溯增加了负担。
既然有成本,为什么还要用?
虽然 ARC 有成本,但它带来的收益通常远超这些微小的性能损耗:
-
逻辑优化(ARC 独有的黑科技) :
正如之前提到的
objc_autoreleaseReturnValue,这种“所有权瞬移”优化在 MRC 下很难手动实现。 -
避免人为错误:
MRC 时代的内存泄露和野指针崩溃导致的调试成本(和用户流失)远比那 1% 的 CPU 损耗贵得多。
-
常态化性能:
手动优化往往只有顶级高手能做到极致。对于 90% 的开发者,ARC 生成的内存管理代码比他们手写的要更高效、更科学。
总结:性能成本对比表
| 维度 | MRC (高手手写) | ARC |
|---|---|---|
| 基础 retain/release | 极低 | 低 (有少量冗余) |
| 弱引用处理 | 手动模拟 (极难且危险) | 较高 (但极其安全) |
| 所有权转移 | 中等 | 极低 (有编译器优化) |
| 开发效率 | 低 | 极高 |