跳到主要內容
Flow

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 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,兩者一起獨立 commit。結果不可比較時(環境不符、視窗未放好、沒有 Flow 結果)拒絕記錄。
  • 公平:各播放器以相同條件播放:VideoToolbox 硬解、不讀使用者設定、不續播、同一視窗大小且在最前。
  • 隔離:每次播放獨立的影片副本,不還原也不保存視窗,結束後還原使用者的視窗設定。
  • GPU:每行程 GPU 時間與整機使用率都從 IORegistry 讀取,不需 root;WindowServer 的 CPU 需 root,不量。
  • 判讀:以總指令數與能耗比較工作量;CPU%、GPU% 受排程與頻率影響(§5.1)。