Android Performance Tuning
與 Embedded Software Engineer 相關的問題,歡迎透過 Linkedin 與我聯繫
Low memory killer daemon 在 AOSP (Android Open Source Project) 的官方文件當中有介紹,不過此文件維護的速度跟不上目前最新的程式碼,因此這篇文章會介紹目前在 Android 14 搭配 Linux 5.15 上最新的機制
如 lmkd 的命名,主要是要針對當系統的 System Memory 觸發低水位警報時,藉由 Kill APKs 來釋放出記憶體,以達到順暢的使用者體驗。因此這個機制在 Low Ram Device 格外重要,參數設定的好,可以使 Memory 處於穩定狀態,達到很好的使用者體驗
PSI 從 GKI (Generic Kernel Image) 出來之後,會強制打開
CONFIG_PSI
,成為 lmkd 默認的低水位警報器,當然 PSI 不只有監測 Memory 而已,連 CPU and IO 都可以監測,但 lmkd 只看 Memory 而已,所以下面只會針對 Memory 介紹
首先先來看一下在 Kernel 當中如何算是 Memory Stall,這在 Kernel PSI 的 Source Code 當中有給相當清楚的描述,只要是被 psi_memstall_enter
and psi_memstall_leave
所包起來的 Section 就稱為 Memory Stall Section,接下來只要把所有在 Kernel Source Code 當中有用到這些 API 的地方找出來就知道 PSI 的 Memory Stall 會是在哪裡
透過 cscope
搜尋的結果如下,可以發現 Memory Stall Section 包含 Directly Reclaim 之外,也有 Disk IO
等待的時間,甚至有等待 Read Swap Page 的時間,因此需要先知道平台主要貢獻者是哪種 Memory Stall Section,才比較好進行下一步的動作
任何進行 Performance Tuning 之前要先蒐集足夠證據再開始動作,否則可能忙半天才發現完全走錯方向浪費不少時間,因此在做任何動作前,要先反問自己一句話,接下來要進行的調整是基於事實的推測,還是盲目的猜測
cscope for psi_memstall_enter
有點經驗的開發者,聽到這邊可能就會直接選擇使用 BCC (BPF Compiler Collection),來動態觀察是哪些 Call Trace 會走到 Memory Stall Section,效率會比直接 grep
來的有效率不少,因為可以知道目前你的平台主要是進入到哪裡的 Memory Stall Section,甚至可以在自己寫 bpftrace 直接觀察到哪個 Section 是主要的貢獻者
bcc stackcount psi_memstall_enter
了解 Memory Stall Section 之後就要來看 PSI 的 Trigger 機制,在 PSI 當中有兩個指標 SOME
and FULL
SOME
- 只要在 Run Queue 上有進入 Memory Stall Section 的 Task 就算是 SOME
的計算範圍,可以看下方程式碼第 9 行FULL
- 除了有進入 Memory Stall Section 的 Task 之外,沒有其他非 Reclaimer (如 kswapd
) 的 Task 在 Run Queue,這樣才算 FULL
的計算範圍,可以看下方程式碼的 13 行,就是在確認 Run Queue 上是否有其他非 Reclaimer 的 Task有時候 Kernel 的文件更新很慢,直接看 Source Code 有時候可以看到很詳細的 Comment 解釋,所以建議看 Document 的同時,也要順便去看 Source Code 上面有沒有更詳細的解說,有時候很納悶為什麼 Maintainer 不一起把 Document 更新一下
接下來就可以來看 PSI 提供給 User Space 的接口該如何使用,這在 PSI Document 已經有給很詳細的描述,User 可以透過 poll()
主動去 Monitor PSI,每當觸發條件就會發 Event 到 User Space,此外條件是可以由 User 自行定義的,可以自訂 SOME
or FULL
以及在多少的 window time
裡面占多少 stall time
就觸發 Event
To register a trigger user has to open psi interface file under /proc/pressure/ representing the resource to be monitored and write the desired threshold and time window. The open file descriptor should be used to wait for trigger events using select(), poll() or epoll().
回頭看 Android 14 當中是如何去 Register PSI Interface,可以看到在 init_psi_monitor
裡面的第 22 行會去定義 stall_type_name
, threshold_us
, and window_us
,這些都是可以透過控制 lmkd 的參數去決定,在後面的內容會提到。另外可以在 register_psi_monitor
裡面看到 lmkd 確實是透過 epoll()
去 Moniter 所設定的條件是否被觸發,假如觸發就去執行對應的 Event Handler
system/memory/lmkd/libpsi/psi.cpp
lmkd 會從 Kernel 所提供的 System Memory Information 去獲取目前系統的 Memory 狀況,來去計算目前的 Memory 水位、threashing 是否嚴重、Zram Swap 用量是否快抵達上限、各個 Task 的 oom_score_adj
分別是多少,了解這些資訊後,再去看 lmkd 的 Code Flow 才會理解其計算的意義
/proc/meminfo
This file reports statistics about memory usage on the system.
會提供系統角度的 Memory 使用量,可以用其資訊計算出目前的水位狀況是在哪個等級,可以從 Understanding the Linux Virtual Memory Manager 看到一張非常經典的水位圖
kswapd
進行 Reclaim Page,直到水位回到 High 才停止進行 Reclaimkswapd
Reclaim Page 的速度比 Allocate Page 的速度還慢,就會導致水位持續降低,低到 Min 時就會觸發 Directly ReclaimFigure 2.2. Zone Watermarks
Understanding the Linux Virtual Memory Manager 是非常經典的 Linux Kernel Memory 的書,即使是基於 Linux 2.6 寫的,但很多基本的觀念到現在 Linux 6.13 也還是通用,非常推薦想了解 Linux Kernel Memory 的開發者閱讀
/proc/vmstat
This file displays various virtual memory statistics.
會統計目前所有 Page 的使用狀況,並且以 Page 為單位去進行計算,其中對於 lmkd 最重要的是會從 workingset_refault_file
去計算 threashing 的狀況,至於 Kernel 是如何定義 threashing 可以去看 Source Code 的解釋,目前筆者沒有詳細閱讀過,就不誤人子弟
mm/workingset.c
/proc/$(pid)/oom_score_adj
This file can be used to adjust the badness heuristic.
lmkd 決定要砍 Task 的話會從 oom_score_adj
高的開始砍,其中每個 oom_score_adj
在 AOSP 有清楚解釋其代表的意義,例如 900 以上代表可以隨意砍掉不會造成 System 任何影響,反之 0 代表 User 正在使用在地 Task,砍掉會造成 User 巨大的影響
services/core/java/com/android/server/am/ProcessList.java
lmkd 最重要的 API 就是 mp_event_psi
,這是 PSI Event Handler Function,會去計算目前系統的 Memory 狀態,並且檢查是否符合 Kill Condition,最後再決定要砍哪個 APKs,這個 API 看熟就等於掌握 lmkd 最核心的地方
oom_score_adj
下是否從 Memory 用量多的 Task 先砍kill
是否執行完畢,假如還在執行並且執行時間還小於 kill_timeout_ms
,就忽略這次的 Eventswap_free_low_percentage
就會進入所謂的 swap_is_low
,假設 Swap Total Size 是 500 MB and swap_free_low_percentage
設定 10%,那只要 Swap Free Size 低於 50 MB 就會進入 swap_is_low
SOME
的 stall ms,window us 是 1000 msFULL
的 stall ms,window us 是 1000 msoom_score_adj
比 lowmem_min_oom_score
大的 TaskFULL
所觸發swap_is_low
並且 thrashing
比設定的 thrashing_limit
還高swap_is_low
thrashing
比設定的 thrashing_limit
還高thrashing
比設定的 thrashing_limit
還高oom_score_adj
高於 lowmem_min_oom_score
由前面介紹可以知道 lmkd 不僅僅調 lmkd 參數而已,其中還牽扯到 Linux Kernel Virtual Memory 的參數設定,如 swappiness
、watermark_scale_factor
、min_free_kbytes
,更不用說還有很多 Android 的參數設定,因此調整參數前要先理解這些參數對應的交互關係,並且觀察平台目前是遇到甚麼瓶頸,蒐集數據找到關鍵,再開始進行調整,千萬不要一股腦地開始調參,這樣往往不會得到好的結果
假如讀者有遇到相關的問題,歡迎 Linkedin 連絡討論