Flow Benchmark
目標:開發的每個階段都能控制 CPU、記憶體、binary size、pass 數與 zero-copy。 效能需求見 REQUIREMENTS.md;可觀測性見 ARCHITECTURE.md §11。
本文件只寫設計、指引與規範。指標、情境、門檻、語料等規格以程式碼為準,不在此重述(§7)。
1. 原則
- 結構以計數驗證:pass 數、拷貝、配置由 runtime 與
FlowMetrics計數器輸出,以確定性指標完全比對,不靠量測推斷。 - 依雜訊分級:確定性指標每個 PR 硬性 gate;量測型指標在固定機器上比對 baseline 與預算。
- Minimal 情境為基準:只含
.essential模組的情境永久保留,用來量測抽象化稅(§9)。 - 單一工具:量測、baseline、gate、報告全用 package-benchmark。
- 量到的就是發行的:Playback 情境與 App 共用 composition root;size 以發行設定量測。
2. 指標
- 類型與 gate:
- 確定性(每次結果相同,如 pass 數、zero-copy 計數器、熱路徑配置次數、binary size):完全相等或不超過上限。
- 低雜訊(如每幀指令數、資料平面配置次數、解碼 surface):對 baseline 的相對回歸。
- 量測(需多次取樣,如 CPU%、footprint、GPU 時間、喚醒、首幀、pacing、能耗):預算,並對 baseline 的相對回歸;受排程與頻率影響者不比 baseline(§5.1),雜訊過大者只記錄。
- 命名:值只能是整數,小數以
X100後綴表示;單位由名稱後綴表示(如Bytes、Microseconds),網站依此格式化。指標預設為成本(越小越好),相反者須宣告。 - 歸屬:分執行緒、分行程的指標以
threads.<name>.、processes.<name>.為前綴;total開頭者含外部行程。
3. 類別
依 scripts/bench.sh 子指令分類;用法見 scripts/bench.sh --help。
size:發行產物的大小與啟動時載入的 dylib。每個 PR;確定性 gate。micro:熱路徑元件與 Graph 編譯。每個 PR;配置次數為確定性,指令數對 baseline,耗時只顯示。e2e:播放情境(§6)。每晚;PR 加標籤時觸發;確定性指標與預算,量測型指標對 baseline。compete:與其他播放器比較。手動;只報告(§10)。
4. 結構指標
4.1 Pass 數
- 一致性:runtime 回報每幀實際編碼的 pass 數,須與
DisplayPlan相等。 - 回歸排查:
DisplayPlan的每幀讀寫 bytes 估算是 GPU 成本的確定性替代指標,先看它,再看 GPU 時間。
4.2 Zero-copy
- 計數器:
FlowMetrics.Counter,以 package traitMetrics啟用;Release App 不啟用,呼叫即編譯移除。 - 穩態:暖機後量測區間內計數為 0。必要的拷貝(VapourSynth 邊界、軟解寫入 pool)由 plan 登記為跨 runtime 邊界成本,數量須完全相等。
- 身分檢查:解碼輸出的 IOSurface 須等於第一個 pass 取樣紋理的 IOSurface,逐幀檢查。
- Lint:
scripts/check-zero-copy.sh(prek)禁止模組原始碼 CPU 存取像素與 GPU 讀回、上傳;例外須列入scripts/zero-copy-allowlist並附理由。
5. 量測
5.1 CPU
- 以每幀指令數判斷退步:雜訊低,用來抓抽象化稅;含外部行程的總指令數是 CPU 工作量的相對回歸 gate。
- CPU% 不比 baseline:同樣的工作排在 E-core 低頻時 CPU% 可高數倍、能耗反而較低,而每幀指令數不變。故 CPU% 只用於預算,以 P-core 時間佔比解讀其變化。
- 跨行程成本:VT 硬解在
VTDecoderXPCService、合成在WindowServer。PERF-01 須計入VTDecoderXPCService;整機數據由 self-hosted 機器以powermetrics取得。 - 分執行緒:分別量測各資料平面執行緒,回歸時定位到階段。
5.2 記憶體
- 圖形記憶體分開:GPU 驅動在行程首次提交 GPU 工作時配置每行程 context,大小取決於用過的 command 類型而非內容,GPU 閒置後才釋放。故 footprint 拆成圖形與非圖形,只 gate 後者;暫停、EOF 情境在 GPU 閒置後量測。
- 解碼 surface 另計:VT 硬解的 surface 屬於
VTDecoderXPCService,不計入本行程 footprint,故以 presenter 持有的 surface 估算解碼 pool。MTLDevice.currentAllocatedSize對同一 surface 的各平面重複計算,不能用來估算。 - 配置次數:Playback 不用 malloc-interposer(§7),改以 libmalloc 的
malloc_logger依執行緒計數。Hook 讓每次配置走慢速路徑,故在量測區間之後另開配置區間,不影響量測。 - 洩漏:長時間播放 footprint 斜率約為 0;反覆 seek 或開關檔後回到 baseline。
5.3 Binary size
- 量測對象:發行產物(§1),含主執行檔、各 framework、metallib;DMG 待 PLAT-06 納入。
- 模組歸屬:以 link map 依 package 加總,PR 輸出 diff。
- FFmpeg:configure 啟用的元件清單做 snapshot。
- 啟動:追蹤啟動時載入的 dylib 數(影響 PERF-02)。
5.4 暫停、首幀、pacing
- 暫停(PERF-04):暫停與 EOF 後不提交、不 vsync,引擎執行緒不耗 CPU、不配置,以確定性指標驗證。
- 首幀(PERF-02):行程內 signpost 讀不到,故以 player 事件時間拆解引擎部分,區間與 signpost 對應。行程啟動與 cold/warm 待以子行程啟動 App 量測。
- Pacing:由實際顯示時間計算 jitter 與丟幀。Offscreen 以 GPU 完成時間代替:準時的幀在其 vsync 顯示,遲到的延到下一個刷新。
6. 情境與語料
- 情境:每個 PERF 需求至少一個情境,另有驗證取樣與 benchmark 自身底線的情境。
- 隔離:每次 iteration 從建立 workload 開始,只量暖機與操作之後的區間;取樣與配置在量測區間外。
- 語料來源:解碼成本依內容而異,以真實影片為主(如 Blender 開放電影,CC-BY),並以 ffmpeg 生成邊界案例(VFR、超長 GOP)。語料不入庫,下載項須固定 SHA-256。下載工具完成前以
scripts/make-fixtures.sh --bench生成的固定碼率雜訊影片代替。 - 覆蓋:
- 1080p H.264 8-bit SDR:Minimal 情境
- 4K HEVC 10-bit HDR10 60p:PERF-01
- 8K HEVC:PERF-03
- 4K AV1:dav1d、PERF-03
7. 基礎設施
- 規格來源:
- 指標:
Benchmarks/Playback/PlaybackMetrics.swift、Benchmarks/Compete/CompeteMetrics.swift、FlowMetrics.Counter - 情境:
Benchmarks/Playback/Scenario.swift、Benchmarks/Compete/Benchmarks.swift - 門檻:
Benchmarks/Thresholds/(預算)、Scenario與PlaybackMetrics(對 baseline)、Benchmarks/Size/Benchmarks.swift - 語料:
Benchmarks/Corpus/corpus.json,欄位見Benchmarks/Support/Corpus.swift - 環境檢查:
Benchmarks/Support/Environment.swift - 指令與環境變數:
scripts/bench.sh --help History/格式:scripts/bench.sh的summarize
- 指標:
- 獨立 package:
Benchmarks/為獨立 SwiftPM package,不進 App;package-benchmark 只用於此,不隨 App 發行,授權列於THIRD_PARTY_NOTICES.md。 - 單一入口:一律經
scripts/bench.sh,它處理建置、plugin sandbox、malloc-interposer、重跑與 baseline;直接使用 plugin 時參照腳本的參數。 - 行程:package-benchmark 每個 benchmark 各啟動一個行程,行程生命週期峰值只屬於該情境。
- Playback 不用 malloc-interposer:它與 AVAudioEngine、Foundation 的釋放衝突,故 Playback 另行建置,配置次數改由
MallocCounter計數(§5.2)。也不用會啟動取樣執行緒的內建指標,以免增加喚醒。 OffscreenPresenter:CI 不需螢幕,以虛擬 vsync 渲染到 IOSurface,由 benchmark 自行註冊。CAMetalLayerdrawable 與合成成本不同,self-hosted 機器另跑螢幕顯示模式。
8. Baseline 與 Gate
- Baseline 只在本機:package-benchmark baseline 只供本機 gate,不入庫;入庫的是
History/。本機 baseline 須是History/最新紀錄,否則只比Thresholds/;換機器、重新 clone 或拉到新紀錄後先跑baseline。 - Gate 機器:M4 Max(baseline
m4-max);PERF 需求以此機器驗收。 - 環境:結果只在環境檢查通過時可比較;略過檢查的結果不得記錄為 baseline 或
History/。Offscreen 不依賴螢幕,螢幕狀態只記錄。 - 判定:
- 確定性指標:對
Thresholds/與 baseline 都須相等。 - 預算:
Thresholds/的 p90 為絕對上限,手寫,不用thresholds update。 - 量測型指標:多次取樣比中位數,超過門檻且重跑仍超過才失敗。
- 雜訊底線併入各 metric 的百分比:package-benchmark 的 relative 與 absolute 任一超過即失敗,無法取 max。
- 不 gate 的 metric 須明列空門檻:package-benchmark 對未列者套預設門檻。
- 確定性指標:對
- 更新 baseline:
scripts/bench.sh baseline,History/獨立 commit,須經 review。 History/格式:變更須以 jq 遷移既有紀錄,並同步website/src/lib/benchmark/。
9. 與開發階段的關係
- Minimal 之前:建立
FlowMetrics、size 報告、Playback 情境骨架。 - Minimal 完成:記錄 baseline v0,作為下限。
- 抽象化稅:之後每個 PR 在 Minimal 情境須符合下列預算(初始值依數據調整,數值見 §7 的門檻):
- pass 數、拷貝數、熱路徑配置次數不變。
- 每幀指令數與只含
.essential模組的 binary size 的增量不超過預算。 - 已註冊但停用的
.optional模組,熱路徑指標等於 Minimal。
- 擴展:新節點或模組須附情境。
10. 競品比較
- 目的:同一台機器、同一支影片,比較 Flow.app 與 IINA、mpv 的實際播放成本。競品與螢幕不受控,只報告,不 gate、不進 baseline。
- 記錄:發行前以
scripts/bench.sh compete --record追加到History/Compete/並更新COMPETE.md,兩者一起獨立 commit。結果不可比較時(環境不符、視窗未放好、沒有 Flow 結果)拒絕記錄。 - 公平:各播放器以相同條件播放:VideoToolbox 硬解、不讀使用者設定、不續播、同一視窗大小且在最前。
- 隔離:每次播放獨立的影片副本,不還原也不保存視窗,結束後還原使用者的視窗設定。
- GPU:每行程 GPU 時間與整機使用率都從 IORegistry 讀取,不需 root;
WindowServer的 CPU 需 root,不量。 - 判讀:以總指令數與能耗比較工作量;CPU%、GPU% 受排程與頻率影響(§5.1)。