從 AGP 9.2(9.2.0-alpha05)開始,Android 上協程啟動和取消速度提升兩倍

從 AGP 9.2 (`9.2.0-alpha05`)開始,針對 Kotlin 協程內部大量使用的 `Atomic*FieldUpdater` 場景,R8 會進行優化操作,把一部分原本較重的 Updater 路徑,改寫成更接近 `Unsafe` 的低層存取形式,從而做到「啟動 / 取消」這類高頻路徑可以明顯加快。

當然,這個處理不是說 Kotlin 本身變快,也不是協程效能完全提升了 2 倍,而是命中了這類原子狀態機熱點路徑時收益帶來的提升

在協程內部一直有長期大量依賴 Atomic*FieldUpdater(AFU) ,這在 kotlinx.coroutines 自帶的 ProGuard / R8 規則檔案也寫了,大多 volatile 欄位是透過 AFU 更新的,並且連標準庫裡的 SafeContinuation 也用了 AtomicReferenceFieldUpdater,也就是說 R8 一直可以感知到協程內部這套原子欄位更新機制的存在。

而實際上 AFU 在 Android 上確實是一個已知問題,2023 年就有人在 kotlinx.coroutines 的 #3950 issue 裡提到:

AtomicReference 相比 atomicfu/Atomic*FieldUpdater 後端,在「write」上大約能快 2 到 4 倍,在 Compose benchmark 的 method trace 裡,很多 JobCoroutineContext 操作被 Atomic*FieldUpdater 呼叫以及對應的型別檢查佔了大量時間。

實際上 Android 官方 API 文件明確表示過,AtomicReferenceFieldUpdater 是一個 reflection-based utility(基於反射的工具),也就是反射式的工具,建立 updater 時還需要 ClassfieldName 去做反射配對和檢查:

官方的文件還特別提到過,它的 compareAndSet 甚至比其他 atomic 類更弱,並且 newUpdater(...) 可能因為欄位不可存取、型別不對、不是 volatile 等原因拋出例外,換句話說,它的設計目標本來就是「用更通用的方式操作欄位」,不是「低開銷實作」。

而對於協程啟動和取消,它本質是一套狀態機切換

  • 建立 Job / Continuation
  • 把狀態從初始態切到活躍態
  • 註冊父子關係
  • 取消時做 CAS(Compare-And-Swap,比較並交換)更新
  • 解除句柄、恢復 continuation、傳播取消

這些步驟裡,很多地方都依賴對 volatile 欄位做原子讀寫和 CAS,也就是主要集中在 JobCoroutineContext 等協程基礎設施的原子操作上。

如果底層用的是 Atomic*FieldUpdater,每次寫入、CAS、lazySet 都要經過一層 updater 語意,而這類 updater 是反射式工具,還帶有型別 / 存取相關約束,所以如果 R8 能在編譯期證明:

  • 這個 updater 綁定的是哪個具體欄位
  • 欄位存取是安全的
  • 呼叫點滿足替換條件

那麼它就可以把「透過 updater 間接操作欄位」的邏輯,改寫成更底層和直接的實現,類似於把多條機械指令,變成一條簡單的機制命令

當然,前面我們說過,這裡的 2x 並不是說「 suspend fun 業務邏輯整體都會 2x」,而是當效能主要卡在協程框架本身的建立、狀態切換、取消傳播這些「原子狀態機開銷」上時,R8 的這次優化可能讓這部分路徑接近 2 倍

那為什麼是 R8 做,而不是 Kotlin / coroutines 自己做? 那是因為 kotlinx.coroutinesatomicfu 需要保持:

  • 通用 JVM 相容性
  • 語意正確性
  • 跨版本可用性
  • 不同執行環境 / 不同平台的一致行為

函式庫層面很難直接寫死成 Android 私有的低層存取形式,而 R8 不一樣,它在構建 app 時做的是 whole-program optimization(整體程式優化),它可以知道:

  • 你最終打包到 Android
  • 具體類、欄位、呼叫點是什麼
  • 哪些路徑可以安全內聯、折疊、改寫

Android 官方也明確把 R8 定義為不僅做 shrink/minify(縮減/混淆),還會做 runtime performance oriented rewriting(執行時效能導向的重寫) 的優化器,它會透過內聯、邏輯優化、類合併等方式重寫程式碼,提高執行效率。

所以這次優化核心是:函式庫繼續保持通用實作,Android 發布鏈路裡的 R8 負責把它處理為更適合 Android 執行時的版本

也就是,*R8 會把 `AtomicFieldUpdater的抽象呼叫,改寫成與底層Unsafe` 存取語意等價、或者足夠接近的實作路徑,類似於在編譯期去抽象層、去反射層、去額外檢查層**等處理。

所以,這次提升並不是 kotlinx.coroutines 本身的提升,也就不是整個 KMP 的提升,而是 AGP 上 R8 針對 Android 平台對 kotlinx.coroutines 的優化帶來的提升,R8 在 release 組建時,把協程內部部分 AFU 熱點重寫成更低層的等價原子存取,從而讓協程啟動 / 取消這類狀態機路徑顯著加速

那你打算升級到 AGP 9.2 嗎?


原文出處:https://juejin.cn/post/7626295537651105792


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝5   💬6   ❤️3
425
🥈
我愛JS
📝2   💬7   ❤️2
206
🥉
💬1  
4
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登