在 Dispatch 篇看過「呼叫如何被翻譯」後,這次來看「值如何被配置」。
本文接續前一篇文章 Swift 深入解析 #5。
struct 的大小是屬性大小的總和嗎?不是。
如果只改變屬性宣告順序,就可能改變 struct 大小,令人意外嗎?本篇是記憶體配置篇。
設計要放入陣列的數十萬個模型,或 Instruments 的記憶體圖比預期肥大時,這些知識就很重要。
也能回答 some·any 篇留下的問題:「那個盒子實際有多大?」
三種尺寸 — size、stride、alignment
Swift 標準提供查詢型別記憶體尺寸的工具:MemoryLayout。
MemoryLayout<Int>.size // 8
MemoryLayout<Bool>.size // 1
struct Point { var x: Double; var y: Double }
MemoryLayout<Point>.size // 16
尺寸有三種,意義各不相同。
size 是單一值的連續記憶體 footprint,不包含型別尾端的對齊填充。
alignment 是型別可放置位址的規則。CPU 從 8 的倍數位址讀取 8 位元組值通常更有效率,某些架構甚至要求如此。
因此每種型別都有「放在 n 的倍數位址」的限制。以下數值以目前 64 位元 Apple 平台為準:Double 的 alignment 是 8,Bool 是 1。
stride 是陣列中到下一個元素的距離。對齊限制可能在元素間產生填充,因此 stride ≥ size。
這是展示三者關係的經典範例。
struct Bad {
var flag: Bool // 1位元組
var value: Double // 8位元組 — 8的倍數位址
var count: Int32 // 4位元組
}
MemoryLayout<Bad>.stride // 24
struct Good {
var value: Double // 8
var count: Int32 // 4
var flag: Bool // 1
}
MemoryLayout<Good>.stride // 16
內容相同,只調整順序,陣列就能節省 33%。Bad 必須在 flag 後為 value 對齊而加入 7 位元組填充(Swift ABI Type Layout)。
先排列 alignment 較大的屬性,是減少填充的常見經驗法則。但不保證所有型別都最佳,最終結果應用 MemoryLayout 實測。
目前 Swift 的 fragile struct 配置演算法依宣告順序放置儲存屬性。但欄位順序不是語言或 ABI 的保證(Swift ABI · SE-0260)。需要精確 offset 的程式碼不可依賴觀察結果。
這項最佳化只有在實例很多時才有意義。設定物件多 8 位元組不重要,但 100 萬元素陣列每個多 8 位元組就是 8 MB(Swift ABI Type Layout)。
一如往常,先量測。
enum 的魔法 — Optional 標籤免費的時候
enum 配置展現 Swift 編譯器的巧思:必須儲存 case 標籤時,會把它藏進 payload 型別未使用的位元樣式(extra inhabitants)。
代表案例是 Bool?。目前 64 位元 Apple 平台上,MemoryLayout<Bool?>.size 是 1。
Bool 在 1 位元組中只使用 0、1 兩種樣式,因此可用剩餘樣式表示 nil。
參考型別 Optional 也相同。Swift 類別指標不把 null 當作物件,因此以 0 表示 nil。目前 64 位元下,AnyObject? 的 size 是 8。
但不是所有 Optional 都免費。Int 使用 8 位元組的所有位元樣式表示值,因此目前環境下 Int? 的 size 是 9、stride 是 16(Swift ABI)。
「安全幾乎免費」只適用於可重用多餘表示的型別。Optional 語法本身不保證所有型別都零記憶體成本。
實測盒子 — 單一協定 existential 是 5 個 word
some·any 篇只說過「any 是有成本的盒子」,現在可以實測。
protocol Shape {}
MemoryLayout<any Shape>.size // 40 — 目前 64位元環境
目前 64 位元環境中,單一非類別限制協定 any Shape 是 40 位元組,也就是 5 個 word:3 個 word 的值緩衝區、型別 metadata 指標,以及一個 witness table 指標。
關鍵在於值的 size 與 alignment 是否都符合 3 個 word 的 inline 緩衝區條件。
超過條件時,值會配置到獨立儲存空間,緩衝區只放指標。此時可能增加 heap 配置、參考計數與間接存取成本(Swift ABI · SE-0335)。
5 個 word 不是所有 any 型別的固定大小。協定組合可能增加 witness table 指標。
僅限類別的協定 existential 使用更小的配置,不需要值緩衝區與額外 metadata 指標。
若協定導向程式碼比預期慢,可考慮用 some·泛型移除盒子,或縮小承載型別。實際效果必須用 Instruments 與基準測試確認(SE-0335 Existential any)。
目前 64 位元 Apple 平台上,類別型別值的 stride 是 8,代表參考大小,不是物件大小。
原生 Swift heap 物件通常有包含 metadata 指標與 inline 參考計數資訊的標頭;64 位元下為 16 位元組。實際配置可能因屬性對齊與 allocator 捨入而更大(Swift HeapObject · Swift RefCount)。
ARC 篇提到的參考型別隱藏成本,就是這個標頭及配置、釋放、計數操作。若把數十萬個小資料片段做成類別,管理成本可能高於資料本身。
實務工具箱 — 重新量測、確認、保持懷疑
整理處理這一層的實務工具。
**用 MemoryLayout 驗證假設。**設計大量產生的模型 struct 時,養成量一次 stride 的習慣即可。
也可在單元測試中加入如 #expect(MemoryLayout<Tick>.stride <= 32) 的預算,日後增加屬性超出預算時會立即顯現。
**用 Instruments Allocations 檢查 heap。**若「明明以 struct 為主卻有很多 heap 配置」,嫌疑對象很明確。
any 盒子、閉包捕獲、CoW(copy-on-write)儲存區,以及類別實例。Allocations 的型別統計會指出是哪一項。
**copy-on-write 集合的錯覺:**目前 64 位元 Apple 平台上,MemoryLayout<[Int]>.stride 是 8。Array 值是間接參考 heap 儲存區的小型 struct,但不可將此數值視為固定 ABI。
這就是不能因為含陣列的 struct stride 很小,就判定它「很輕」的原因。
真正的重量在 heap 儲存區,由 Allocations 顯示。CoW 原理如另一篇文章所述。
最後談平衡感:這一篇的知識並非每天都會用到。
但知道「為什麼」的價值會在問題發生時顯現。記憶體圖異常時,可以從嫌疑清單而非猜測開始,這就是深入一層的優勢。
總結
- 型別有三種尺寸:size 是不含 tail padding 的 footprint,alignment 是位址對齊規則,stride 是連續配置間距。大量產生的 struct 常可藉由集中高 alignment 屬性減少填充,但必須實測。
- enum 可將標籤藏進 extra inhabitant。Bool? 與參考型別 Optional 不需額外空間,但 Int? 等型別需要獨立標籤空間。
- 64 位元單一非類別協定 existential 是 5 個 word。值的 size 或 alignment 超過 3-word 緩衝區條件時,可能產生獨立儲存與間接成本。
- 目前 64 位元原生 Swift 物件的基本標頭通常是 16 位元組。實際配置可能因屬性與 allocator 對齊而更大。
- 工具是 MemoryLayout(設計驗證)與 Instruments Allocations(實測)。沒有量測的配置最佳化就是過早最佳化。
下一篇討論編譯時的魔法:在 #Preview 與 @Observable 背後讓程式碼撰寫程式碼的 Swift 巨集。
來源與確認標準
- MemoryLayout — Apple Developer Documentation · 確認 2026-08-19 · 依據:size、alignment、stride 的 API 定義
- Swift ABI Type Layout — Swift 專案 · 官方文件 · 確認 2026-08-19 · 依據:struct、enum、existential container 配置
- SE-0260 Library Evolution — Swift Evolution · 確認 2026-08-19 · 依據:struct 欄位順序的語言與 ABI 保證範圍
- SE-0335 Existential any — Swift Evolution · 確認 2026-08-19 · 依據:existential 的 3-word inline 緩衝區與動態記憶體成本
- Swift HeapObject · Swift RefCount — Swift Runtime · 確認 2026-08-19 · 依據:原生 heap 物件標頭與 inline 參考計數

![[Swift 深入解析 #6] Swift 記憶體配置:屬性順序會改變大小 封面圖](/assets/images/posts/dfd50aa0-6496-4f00-a59f-21b04c18ed87/swift-memory-layout-size-stride-alignment.jpg)