一、先建立分析心智模型(非常关键)
Swift 中只有两类“慢源头”
-
值类型路径
- struct / enum
- 问题来源:复制、stride、COW 失效
-
引用类型路径
- class / closure
- 问题来源:retain / release 抖动、逃逸、堆分配
👉 所有性能问题,都能归到这两类之一。
二、第一步:用“内存模型”快速定性(不用工具)
1️⃣ 看到 struct,就问 4 个问题
struct BigModel {
var a: Int
var b: [Int]
}
问自己:
- size / stride 多大? (> 64B 警惕)
- 是否频繁赋值 / 传参 / 返回?
- 是否频繁写?(会触发 COW)
- 是否在循环 / map / async 中?
如果 ≥2 个是 “是” → 复制热点
2️⃣ 看到 class,就问 4 个问题
class Node {
var next: Node?
}
问自己:
- 是否频繁创建 / 销毁?
- 是否被 closure / async 捕获?
- 是否通过 protocol / AnyObject 传递?
- 是否跨线程?
如果 ≥2 个是 “是” → ARC 压力热点
三、第二步:用 Instruments 定位“哪一种压力”
1️⃣ 查 ARC 压力(引用类型)
用 Instruments → Allocations + Zombies(调试)
关注:
retainreleaseobjc_retainswift_retain
如果看到:
- 高频 retain/release
- 对象生命周期极短
👉 ARC 抖动
2️⃣ 查值类型复制(struct / enum)
用 Instruments → Time Profiler
看:
swift_retain+swift_release(COW buffer)memcpyoutlined copycopy_value_buffer
👉 命中这些,基本就是 值复制
四、第三步:回到 Swift 内存模型做“根因分析”
场景 A:struct 复制热点
models.map {
var m = $0
m.count += 1
return m
}
内存模型视角:
- 每个
$0是一个 完整值拷贝 map生成新数组- 每次写触发 COW
👉 N × size 的复制
修复策略:
for i in indicesinout- 拆 struct
- 手动 COW
场景 B:ARC 抖动热点
for _ in 0..<10_000 {
let obj = Foo()
use(obj)
}
内存模型视角:
-
每次 loop:
- heap alloc
- retain
- release
-
逃逸分析失败(use 不透明)
修复策略:
- 对象池
- 值类型替代
final class- inline / 泛型
五、第四步:判断“该用哪种手段解决”
| 现象 | 根因 | 解决方向 |
|---|---|---|
| memcpy 多 | struct 太大 | 拆分 / 引用内核 |
| swift_retain 多 | COW buffer | 减少写 |
| objc_retain 多 | class 逃逸 | final / 局部化 |
| alloc 多 | 堆频繁 | 栈化 / 复用 |
六、一个真实“翻车 → 修复”的例子
❌ 原始代码
struct State {
var list: [Item]
}
func update(_ state: State) -> State {
var s = state
s.list.append(Item())
return s
}
State在 reducer 中被疯狂复制- 每次 append 都触发 COW
✅ 修复后
func update(_ state: inout State) {
state.list.append(Item())
}
- 单一 buffer
- 无中间复制
- ARC / memcpy 大幅下降
七、进阶:SIL 视角(面试加分)
如果你说一句:
“我会看 SIL 里是否有
copy_value/retain_value,
以及alloc_ref是否被提升为alloc_stack”
面试官基本就知道你不是在背书。
英文版
7-10. [Advanced] How to use the Swift Memory Model to analyze Frequent Copying or ARC Pressure?
I. Establish an Analytical Mental Model (Critical)
In Swift, there are only two primary "sources of slowness" related to memory:
-
The Value Type Path
- Targets:
struct,enum,tuple. - Pain Points: Redundant copying, large strides, and COW (Copy-on-Write) invalidation.
- Targets:
-
The Reference Type Path
- Targets:
class,closure,existential (protocol). - Pain Points: ARC "churn" (retain/release jitter), heap allocation, and escape analysis failure.
- Targets:
👉 Virtually all Swift performance issues can be categorized into one of these two paths.
II. Step 1: Qualitative Analysis via Memory Model
1️⃣ When you see a struct, ask 4 questions:
- Is the size/stride large? (Be alert if > 64 bytes).
- Is it frequently assigned, passed as an argument, or returned?
- Is it frequently modified? (This triggers COW checks or full buffer copies).
- Is it used inside a high-frequency loop,
map, orasynccontext?
If ≥2 answers are "Yes" → You likely have a Copying Hotspot.
2️⃣ When you see a class, ask 4 questions:
- Is it frequently created and destroyed?
- Is it captured by closures or
asynctasks? - Is it passed via protocols or
AnyObject? (Triggers existential containers). - Is it shared across threads? (Atomic RC increments are expensive).
If ≥2 answers are "Yes" → You likely have an ARC Pressure Hotspot.
III. Step 2: Identify the "Pressure Type" using Instruments
1️⃣ Detecting ARC Pressure (Reference Types)
Use Instruments → Allocations + Zombies (for debugging) .
- Focus on:
retain,release,objc_retain,swift_retain. - The Signal: If you see a massive frequency of retain/release calls on objects with extremely short lifespans, you have ARC Churn.
2️⃣ Detecting Value Copying (Structs / Enums)
Use Instruments → Time Profiler.
- Focus on:
swift_retain/swift_release(associated with COW buffers),memcpy,outlined copy, andcopy_value_buffer. - The Signal: High CPU time spent in these symbols indicates Value Copying overhead.
IV. Step 3: Root Cause Analysis via Memory Model
Scenario A: Struct Copying Hotspot
Swift
models.map {
var m = $0 // 1. Full value copy
m.count += 1 // 2. Potential COW trigger
return m // 3. Copy back to new array
}
- Memory Perspective: Every
$0is a bitwise copy.mapallocates a new array buffer. - Strategy: Use
for i in indices,inoutparameters, or decompose the struct to reduce the copy size.
Scenario B: ARC Churn Hotspot
Swift
for _ in 0..<10_000 {
let obj = Foo()
use(obj)
}
- Memory Perspective: Every loop triggers a heap allocation, a retain, and a release. If
use()is opaque, Escape Analysis fails, preventing Stack Promotion. - Strategy: Implement object pooling, switch to value types where possible, use
final class, or leverage generics to aid inlining.
V. Step 4: Decision Matrix for Solutions
| Symptom | Root Cause | Solution Direction |
|---|---|---|
High memcpy | Struct is too large | Decompose / Move to "Indirect" (Reference) Core |
High swift_retain | COW buffer overhead | Reduce write-frequency / Use inout |
High objc_retain | Class/Closure Escaping | Use final, localize scope, avoid Any |
High alloc | Frequent Heap usage | Stack Promotion / Object Reuse |
VI. Engineering Example: From "Failure" to "Fix"
❌ Original Code (High Pressure)
Swift
struct State { var list: [Item] }
func update(_ state: State) -> State {
var s = state // Copy
s.list.append(Item()) // COW check + Potential Reallocation
return s // Copy back
}
✅ Optimized Code (Low Pressure)
Swift
func update(_ state: inout State) {
state.list.append(Item()) // Mutates the original buffer directly
}
- Result: Single buffer usage, zero intermediate copies, significant drop in ARC and
memcpycalls.
VII. Pro Tip: The SIL Perspective (Interview Bonus)
If you mention this to an interviewer, they will know you have a deep understanding:
"I check the SIL (Swift Intermediate Language) to see if
copy_valueorretain_valueare being emitted unnecessarily, and whetheralloc_refis successfully being promoted toalloc_stackby the optimizer."
VIII. Final Summary
The key to analyzing performance via the Swift memory model is to first distinguish between "Value Copying" and "ARC Pressure." Then, evaluate the bottleneck through the lens of size, stride, COW, and escape analysis. Finally, choose a structural refactor (like switching to inout or a COW-backed struct) rather than simple syntax micro-tweaks.