五微秒的差距與21微秒的例外:一份Flutter狀態管理基準測試,攤開的是效能帳該怎麼記
前Google工程師Filip Hráček以五天、2100次trial的完整UIworkload實測Flutter狀態管理庫,結果顯示框架間差距僅數微秒,本文從效能成本結構與開發決策角度拆解這份基準測試的產業意涵。
2026年9月8日,戀貓de小郭在掘金發布文章,討論前Google工程師、Flutter生態知名開發者Filip Hráček近期發布的一份狀態管理基準測試。這份測試的結論本身不戲劇化,甚至可以說相當平淡:Provider、Riverpod、GetX、Signals相對原生setState()的差距,小到幾乎沒有工程意義。真正值得記帳的,是它推翻了整個「狀態管理庫性能排行榜」這類內容的定價基礎。
先看這份測試怎麼花錢
多數人熟悉的狀態管理性能比較,是microbenchmark:寫一個循環,讓Riverpod、Provider、Signals連續通知一萬次,跑出一張吞吐量排行榜。這種測試成本低、產出漂亮、傳播性強,但對真實App場景幾乎沒有參考價值。
Hráček的做法換了一個成本結構。同一個Todo App,用七種狀管理方案各自實現一遍,跑完整的UI workload,而不是孤立的函數調用。為了控制設備噪聲,測試總共花了五天,執行了2100個trial、300個round。以一份開源基準測試的標準看,這個樣本量遠高於社羣裡常見的隨手跑分。
結果落在兩個層面。第一,Riverpod、GetX、Signals大約只比原生setState()增加5到6微秒的build time。以120Hz螢幕換算,一幀的frame budget約8.33毫秒,這個差距佔比不到0.1%。第二個結果更有意思:package:bloc的實現平均比原生setState()快了約21微秒。
一個帶Stream的框架,為什麼會比「什麼都不用」快
後面這個數字如果只看圖,很容易得出「Bloc性能碾壓所有方案」的結論。但一個帶Event、Stream、BlocBuilder的框架,邏輯上不可能比官方最薄的路徑更快。問題出在帳本記錯了欄位。
Flutter官方文檔早已寫明:setState()本身的direct overhead極小,它執行傳入的callback,然後調用Element.markNeedsBuild()。真正喫時間的是後續的subtree rebuild,以及連帶觸發的layout和paint。換句話說,狀態管理庫自身那幾微秒的通知成本,在整個渲染管線的帳本裡是可以忽略的零頭,真正的成本大項是「多少個Widget被標成dirty、多少個build()被重新執行」。
舉一個具體的算術。一個頁面上有100個Widget,某次狀態變化實際上只影響其中一個loading indicator。A方案把監聽放在整個頁面外層,狀態一變,大塊subtree全部重新執行build();B方案把響應狀態的Widget放得很低,只重建loading indicator周圍的一小塊。這時候哪怕B的狀態分發機制本身多花幾微秒,它最終的整幀build time仍可能比A少幾十微秒。Flutter官方性能指南一直在強調同一件事:調用setState()時要盡量localize在真正發生UI變化的subtree,避免把狀態放在Widget Tree太高的位置。
回頭檢查bloc_library的sample就會發現,那份快了21微秒的實現,恰好做了正確的granular rebuild:loading indicator的邏輯被放到了Widget Tree更低的位置。從倉庫設計也能看出來,它沒有用一個全局state讓所有Widget一起監聽,而是把狀態拆成TodosBloc、FilteredTodosBloc等多個Bloc,後者監聽前者,把重建範圍逐層收窄。所以那張圖裡的-21微秒,混進的是Widget Tree結構差異,不是框架本身的性能優勢。
這筆帳影響的是誰的決策成本
把影響推演開來,這份測試動到的是三個羣體的成本表。
對開發團隊而言,選型的加權權重該重排了。既然Provider、Riverpod、GetX、Signals在同一水平上的性能差距不足frame budget的0.1%,那麼框架之間真正可比較的項目就剩下寫法、可維護性、團隊學習曲線和生態成熟度。把選型爭論押在性能欄位上,等於在一個方差小於噪聲的變數上做決策。
對技術內容生產者而言,microbenchmark排行榜的內容折舊會加速。那類「讓框架連續通知一萬次」的跑分文,在這份五天、2100個trial的完整workload面前,方法論上是降級的。讀者一旦理解了「通知成本」與「subtree rebuild成本」是兩個量級不同的欄位,前者就不再有傳播價值。
對框架維護者而言,這份結果其實是減壓的。它意味著狀態管理庫不需要在notifyListeners()或dependency lookup這些微秒級路徑上做極致優化來證明存在價值,競爭的主戰場回到API設計與rebuild粒度的預設行為。bloc_library樣本裡那個拆分多個Bloc的結構,反而是最值得被抄的部分。
收束:帳要記在大欄位上
跨週期看,這不是第一次出現「microbenchmark結論被完整workload推翻」的劇本。CPU產業的Dhrystone、前端框架的DOM操作跑分,都經歷過同一條曲線:孤立指標先被行銷化,然後被真實場景測試校正,最後選型決策回歸到結構性變數。
這份測試留下的可遷移結論只有一條:效能帳的大欄位在重建範圍,不在通知路徑。誰把狀態放得越低、重建範圍收得越窄,誰就喫掉整幀時間的主要變數。至於選哪個框架,那是維護成本的帳,不是性能的帳。
主題