7-8.【高级特性】对于大 struct,如何避免性能问题?

4 阅读2分钟

一、先给结论(面试版)

大 struct 的性能问题,核心不是“值类型”,
而是“拷贝、对齐、cache、以及写路径是否触发 COW”。

解决思路只有三类:

  1. 减少拷贝
  2. 减少内存占用 / stride
  3. 减少写入路径

二、先定义什么是「大 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。