一、先给结论(面试版)
大 struct 的性能问题,核心不是“值类型”,
而是“拷贝、对齐、cache、以及写路径是否触发 COW”。
解决思路只有三类:
- 减少拷贝
- 减少内存占用 / stride
- 减少写入路径
二、先定义什么是「大 struct」
一般认为满足任意一条就要警惕:
- size ≥ 64 bytes(一个 cache line)
- 包含多个
Array / Dictionary / String - 频繁作为参数传递 / 返回
- 被高频修改(尤其在循环里)
struct BigModel {
var id: UUID
var title: String
var tags: [String]
var metadata: [String: String]
var flags: UInt64
}
三、核心策略 1:拆分 + 引用语义内核(最有效)
✅ 把“少变的、大的”放进 class
final class BigStorage {
var tags: [String]
var metadata: [String: String]
}
struct Model {
var id: UUID
var title: String
private var storage: BigStorage
}
效果:
- struct 仍是值语义
- 大数据只传指针
- 修改路径更可控
👉 Swift 标准库 Array 就是这么干的
四、核心策略 2:手动实现 COW(高阶但很值)
什么时候需要?
- struct 很大
- 写操作不多
- 读多写少
struct Model {
private var storage: Storage
mutating func updateTitle(_ t: String) {
if !isKnownUniquelyReferenced(&storage) {
storage = Storage(storage)
}
storage.title = t
}
}
final class Storage {
var title: String
}
📌 注意:
- 所有 mutating 写路径都要检查唯一性
- Storage 必须是
final
五、核心策略 3:减少无意义的拷贝(90% 项目有效)
1️⃣ 避免 value-type 作为频繁参数
❌
func process(_ model: BigModel)
✅
func process(_ model: inout BigModel)
或:
func process(_ model: borrowing BigModel) // Swift 5.9+
2️⃣ 避免链式 map / forEach 修改大 struct
❌
models.map {
var m = $0
m.title = "x"
return m
}
✅
for i in models.indices {
models[i].title = "x"
}
六、核心策略 4:内存布局优化(经常被忽略)
1️⃣ 字段排序,减少 padding
// ❌
struct S {
let a: Int8
let b: Int64
}
// ✅
struct S {
let b: Int64
let a: Int8
}
👉 对数组、批量处理非常重要
2️⃣ 用 bitmask / enum 减少字段
struct Flags {
var raw: UInt64
}
比多个 Bool 更省空间、更 cache 友好。
七、核心策略 5:减少“写路径”
❌ 高频写大 struct
for _ in 0..<10000 {
model.count += 1
}
✅ 局部变量聚合写
var count = model.count
for _ in 0..<10000 {
count += 1
}
model.count = count
八、什么时候该“放弃 struct 用 class”?
这是面试官最爱追问的。
明确可以用 class 的情况:
- 生命周期复杂、共享频繁
- 需要 identity(== 不等于 ===)
- 高并发频繁写
- 非常大的对象(百字节级)
“值语义是工具,不是信仰”
九、一句话终极总结(建议背)
大 struct 的性能问题来自拷贝和 cache;
通过拆分、COW、inout、布局优化和减少写入路径,可以保留值语义同时避免性能灾难;
当共享和高频修改成为常态,应果断使用 class。