# Flow Benchmark

URL: https://flow.lanstar.dev/docs/benchmark

> 目標：開發的每個階段都能控制 CPU、記憶體、binary size、pass 數與 zero-copy。
> 效能需求見 [`REQUIREMENTS.md`](/docs/requirements#perf-效能)；可觀測性見 [`ARCHITECTURE.md`](/docs/architecture) §11。

本文件只寫設計、指引與規範。指標、情境、門檻、語料等規格以程式碼為準，不在此重述（§7）。

## 1. 原則

- **結構以計數驗證**：pass 數、拷貝、配置由 runtime 與 `FlowMetrics` 計數器輸出，以確定性指標完全比對，不靠量測推斷。
- **依雜訊分級**：確定性指標每個 PR 硬性 gate；量測型指標在固定機器上比對 baseline 與預算。
- **Minimal 情境為基準**：只含 `.essential` 模組的情境永久保留，用來量測抽象化稅（§9）。
- **單一工具**：量測、baseline、gate、報告全用 [package-benchmark](https://github.com/ordo-one/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 trait `Metrics` 啟用；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 自行註冊。`CAMetalLayer` drawable 與合成成本不同，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. 與開發階段的關係

1. **Minimal 之前**：建立 `FlowMetrics`、size 報告、Playback 情境骨架。
2. **Minimal 完成**：記錄 baseline v0，作為下限。
3. **抽象化稅**：之後每個 PR 在 Minimal 情境須符合下列預算（初始值依數據調整，數值見 §7 的門檻）：
    - pass 數、拷貝數、熱路徑配置次數不變。
    - 每幀指令數與只含 `.essential` 模組的 binary size 的增量不超過預算。
    - 已註冊但停用的 `.optional` 模組，熱路徑指標等於 Minimal。
4. **擴展**：新節點或模組須附情境。

## 10. 競品比較

- **目的**：同一台機器、同一支影片，比較 Flow.app 與 IINA、mpv 的實際播放成本。競品與螢幕不受控，只報告，不 gate、不進 baseline。
- **記錄**：發行前以 `scripts/bench.sh compete --record` 追加到 `History/Compete/` 並更新 [`COMPETE.md`](/docs/compete)，兩者一起獨立 commit。結果不可比較時（環境不符、視窗未放好、沒有 Flow 結果）拒絕記錄。
- **公平**：各播放器以相同條件播放：VideoToolbox 硬解、不讀使用者設定、不續播、同一視窗大小且在最前。
- **隔離**：每次播放獨立的影片副本，不還原也不保存視窗，結束後還原使用者的視窗設定。
- **GPU**：每行程 GPU 時間與整機使用率都從 IORegistry 讀取，不需 root；`WindowServer` 的 CPU 需 root，不量。
- **判讀**：以總指令數與能耗比較工作量；CPU%、GPU% 受排程與頻率影響（§5.1）。
