Linux 排程器的一個最佳化,讓 Android 多耗 20% 的電,傳音工程師如何發現問題?

一段程式碼以前是好的,但經過多年迭代之後,雖然程式本身沒改,卻可能反而變成問題,這就是這次要聊的主題:

一個原本為了改善 Android UI 流暢度而引入的排程器最佳化,幾年後反而讓 SoC 功耗最高增加 20%

事情的起因是 Linux 排程器裡的 cpu_util() boost 機制。最近傳音控股的工程師 Hongyan Xia 在 LKML 提交了一個 patch:

bash 体验AI代码助手 代码解读复制代码[PATCH] sched/fair: Revert boost in cpu_util()

這個 patch 主要是把 cpu_util() 裡基於 runnable_avg 的 boost 邏輯移除。雖然看起來像是一個排程器的小改動,但背後其實牽涉到 PELT、schedutil、Android 圖形管線、JankBench、ADPF、廠商功耗策略,以及 Linux 主線核心和 Android 真實裝置之間的驗證偏差等問題。

大致情況是,傳音工程師在升級 Linux 核心版本後,發現多款 Android 手機的 SoC 功耗明顯上升,而且在多種真實工作負載下都出現類似情況,不限於某個 App 或某顆晶片。他們測試的場景包括:

  • YouTube 1080p 60 播放
  • 手機遊戲,例如《無盡對決》、《原神》
  • 多種真實手機工作負載

而出現問題的情況是:

  • CPU 頻率更容易維持在高檔
  • 功耗卻明顯增加
  • 部分場景 SoC 功耗增加約 20%

於是他們開始做 git bisect,最後定位到 Linux CPU 排程器和 schedutil CPU 頻率調整路徑裡的 boost 邏輯。這個 boost 邏輯的關鍵點是:

過去 schedutil 主要看 util_avg,後來為了更積極回應 CPU contention,又額外看 runnable_avg;如果 runnable_avg 暗示「排隊工作很多」,就提高 CPU 頻率。

也就是說,系統看到很多任務處於 runnable 狀態,就認為 CPU 可能不夠用,因此更積極地拉高頻率;但傳音工程師實測後發現,這種處理邏輯在現在的 Android 上很不合理,它讓 CPU 更積極提頻,卻沒有換來對應的效能收益

這個問題得從 Linux 排程器聊起。Linux 裡有一個很核心的負載追蹤機制,叫 PELT,全名是 Per-Entity Load Tracking。它大致會追蹤兩個東西:

  • util_avg:任務真正拿到 CPU 執行的時間
  • runnable_avg:任務處於 runnable 狀態的時間,包括正在執行,也包括排隊等待 CPU

這兩個指標在 CPU 競爭場景下差異很大。舉例來說,如果一顆 CPU 上同時有 4 個任務在搶:

arduino 体验AI代码助手 代码解读复制代码Task A
Task B
Task C
Task D

如果它們都很想跑,但每個任務只能拿到 25% 的 CPU 時間,那麼每個任務的 util_avg 看起來都不高,從單一任務角度看:

arduino 体验AI代码助手 代码解读复制代码Task A 實際只執行 25%
Task B 實際只執行 25%
Task C 實際只執行 25%
Task D 實際只執行 25%

於是 util_avg 會顯得偏低,但真實情況是 CPU 已經被塞滿了,這就是 boost 機制當初想解決的問題。

如果排程器只看「任務實際跑了多久」,就可能低估 CPU 需求;如果把「任務排隊等了多久」也算進來,就能更早發現 CPU contention,所以這個設計的初衷是:

util_avg 反應慢,那就引入 runnable_avg
如果 runnable_avg 明顯高於 util_avg,表示有任務在排隊。
任務在排隊,可能表示 CPU 頻率不夠。
那就讓 schedutil 更積極提頻。

但在 Android 上就有點不一樣。Android 手機常見的 CPU 調頻路徑一般可以簡化成:

  • 任務執行 / 喚醒 / 遷移
  • 排程器更新 PELT 訊號
  • schedutil 讀取 CPU utilization
  • 計算目標頻率
  • cpufreq 驅動切換到對應 OPP

這裡主要是 schedutil:每次排程器負載追蹤更新時,例如任務喚醒、任務遷移、時間推進,都會呼叫 schedutil 去更新硬體 DVFS 狀態

也就是說,schedutil 本質上是一個「由排程器驅動的 CPU 調頻 governor」。它根據 CPU runqueue 上的 utilization 估算目前需要多少頻率,利用率越高,目標頻率越高。所以 cpu_util() 裡多加一個 boost,在 Android 上就不是一個無關緊要的小數值,它會直接影響 CPU 頻率選擇

例如在目前主線程式裡,sugov_get_util() 就會呼叫:

ini 体验AI代码助手 代码解读复制代码util += cpu_util_cfs_boost(sg_cpu->cpu);
util = effective_cpu_util(...);
util = max(util, boost);
sg_cpu->util = sugov_effective_cpu_perf(...);

這代表 CFS 的 boost util 會進入 schedutil 的頻率決策鏈路;一旦 cpu_util_cfs_boost() 給出的值偏高,CPU 就更容易被推到高頻。

不過這套機制在過去的 Android 其實是有用的,因為當時 Android UI 的體驗常常「慢半拍」。早期 Android 的排程問題裡:

  • 任務剛喚醒時 PELT 還沒反應過來
  • schedutil 覺得 CPU 不忙
  • CPU 頻率還沒拉起來
  • 結果 UI 執行緒 / RenderThread 錯過 frame budget

當年很多 Android 效能最佳化的思路都偏向「寧可早點提頻,也不要掉幀」。JankBench 這類工具也正是在這種背景下被用來驗證 UI 流暢度,它關注的是 Android 圖形管線,也就是使用者滑動、列表渲染、動畫等場景下的 jank。

所以在當年看來,boost 邏輯有它的歷史合理性,因為 PELT 慢、UI 負載短而急、schedutil 提頻慢,導致使用者看到卡頓,因此用 runnable_avg 提前補一腳油門。

但問題在於,時代變了,這也是這次傳音發現的結論:

  • CPU 頻率確實更高了
  • 但效能沒有明顯變好
  • 功耗卻明顯上升

也就是說,現在 runnable_avg boost 的場景在新一代 Android 手機上不成立,因為它的假設是:

runnable_avg 高
  ↓
CPU contention 高
  ↓
CPU 頻率不夠
  ↓
提高 CPU 頻率能改善體驗

但實測並不是這樣。runnable 多,不一定代表 CPU 頻率不夠。任務排隊可能是 CPU 忙,也可能是鎖競爭、執行緒喚醒風暴、Binder 排程、GPU 等待、記憶體頻寬壓力、thermal 限制,甚至是應用程式本身的執行緒模型不合理。

這時候排程器看到的是很多 runnable task,但系統真正的問題可能是:

  • GPU 忙
  • 記憶體頻寬不足
  • 某把鎖被占住
  • RenderThread 在等 SurfaceFlinger
  • 遊戲主執行緒在等 GPU fence
  • 影片播放受解碼與顯示鏈路限制

所以如果瓶頸不在 CPU 頻率上,提高 CPU 頻率其實不會明顯提升效能。

另外,現在 Android 已經有更直接的 performance hint。過去系統需要靠排程器猜,但 Android 後來已逐步引入更明確的機制,例如 ADPF(Android Dynamic Performance Framework),它支援遊戲和效能敏感 App 更直接地與 Android 的功耗、溫控和 CPU 管理系統互動,而不是讓排程器只靠 runnable / util 盲猜。

所以從這個角度看,runnable_avg boost 是一個比較粗糙的舊時代啟發式規則。

最後,現在廠商通常也有自己的一堆排程和提頻策略。畢竟 Android 手機不是裸 Linux,SoC 廠商、系統廠商、遊戲模式、Power HAL、thermal governor、GPU governor 都可能參與效能決策。

所以如果廠商已經對前台 App、遊戲執行緒、SurfaceFlinger、RenderThread 做了 hint 或 boost,Linux 排程器再根據 runnable_avg 加一層 boost,就很容易出現多層策略疊加,導致「過度提頻」。

傳音這次看到的現象就很像這種過度提頻:CPU 更常待在高頻,SoC 功耗明顯增加,但使用者可見效能沒有明顯變化

所以這其實不算是 Bug,而是當年的最佳化到了今天反而成了負擔。現在系統更成熟了,方案也更多了,所以 boost 自然就成了時代的問題;而 Linux 主線希望機制通用,Android 裝置則希望整機體驗和續航最優,這些年 Android 發展太快,所以 Linux 層面沒跟上也算正常。

所以程式碼不是一開始能跑,就代表一直都能跑;過去的最佳化,在現在也可能變成負債。

連結

lore.kernel.org/lkml/202605…


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


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

共有 0 則留言


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