5-7.【性能分析与优化】如果一个大型 struct 被频繁传递,你会如何优化它的性能?

3 阅读2分钟

1️⃣ 传递方式优化

(1)用 inout 避免不必要拷贝

struct BigData { var array: [Int] = Array(repeating: 0, count: 1_000_000) }

func modify(_ data: inout BigData) {
    data.array[0] = 1
}

var data = BigData()
modify(&data)  // 不会复制整个 struct
  • inout 直接在调用者的内存上操作。
  • 注意:调用者的 data 会被写入,函数内修改会影响外部。

(2)使用指针或 UnsafeMutableBufferPointer

对于极端性能要求:

var data = BigData()
data.array.withUnsafeMutableBufferPointer { buffer in
    for i in 0..<buffer.count {
        buffer[i] += 1
    }
}
  • 避免多次数组索引边界检查
  • 对大型数组、循环修改特别有效

(3)传引用而非值

如果 struct 太大,频繁拷贝:

class BigDataWrapper {
    var data: BigData
    init(_ data: BigData) { self.data = data }
}
  • 将 struct 放到 class 里,通过引用传递
  • CoW 原理依然生效(struct 内部 buffer 可以共享)
  • 注意:class 会改变值语义,可能带来副作用

2️⃣ 减少拷贝开销

(1)拆分大型 struct

  • 把大型 struct 拆成 小 struct + 指向 heap 的 buffer
  • 例如:
struct BigData {
    var metadata: Meta
    var values: [Int]  // 堆上
}
  • 这样 struct 本身很小,传递时只拷贝指针和 metadata
  • 实际大量数据在 heap,拷贝只在必要时触发 CoW

(2)利用 CoW 容器

  • Swift 数组、字典本身是 CoW
  • 不要把数组全改成 class 才能共享内存
  • 频繁读取时共享 buffer,写入时才触发复制

(3)避免无用返回值

func process(_ data: BigData) -> BigData { ... }  
// 返回会触发复制
  • 对大型 struct,尽量用 inout修改内部引用类型,避免每次返回都复制

3️⃣ 内存布局优化

  • 尽量把 小字段放在前,large buffer 放在后,减少拷贝开销
  • 对嵌套 struct,要留意 连续内存访问
  • Swift 5+ 对 struct 优化比较好,但在堆外部 buffer 时性能依然更稳

4️⃣ 总结策略

场景优化手段
频繁传递大型 structinout 或 class 包装
修改大型数组/子 structUnsafeMutableBufferPointer 或 CoW 容器
大型 struct 返回尽量用引用传递,减少返回值复制
多个函数共享同一个数据CoW + heap buffer 或 class 共享

💡 核心原则:

Swift struct 值语义很强,但性能成本来自拷贝大型数据。优化的思路是:
减少拷贝次数 + 利用 heap buffer 或引用 + 避免不必要的返回