前傳
嵌入式系統的內存回收還是比較重要的,因為這塊涉及到程序運行性能。
嵌入式系統(比如平板,手機)會更加關注單機性能優化,因而會更加重視系統內存回收。
嵌入式系統不像互聯網那種大型分布式服務器系統,他們往往內存和存儲容量比較充裕,因而關注點在分布式方面,對單機性能不夠重視。嵌入式系統,在有限的內存和存儲空間因素制約下,會更加關注單機性能優化。
而內存回收這塊是比較重要的,因為內存回收做的不好,內存壓力得不到釋放,最直接的是內存壓力會轉化為IO壓力,對系統io性能造成影響。另外也會轉換為cpu壓力,影響程序的cpu資源使用。
所以結合我對內存回收方面的調研,想重點寫下對Linux內核內存回收這塊代碼的理解,也想分享下我在這塊的調研心得。
內存回收的重要性
這個內存回收方面的優化對系統到底有怎樣的影響,我想舉幾個例子會詳細透徹地講下內存回收方面的優化,還有給系統帶來的好處。
一 、內存回收在高負載系統性能方面的優化
1 問題現象:
移動設備上(比如平板,手機)后臺u盤傳輸大批量數據時,前臺桌面操作卡頓。
2 原因分析:
1: u盤拷貝數據量大時,會導致系統內存里面臟頁變多,然后可用內存變少。
2: 然后前臺平板操作時,會allocate_pages去分配內存頁。這個時候會陷入slow path,去觸發內存回收。
由于此時有大量臟頁存在,內存回收會比較耗時,這樣前臺操作的內存分配性能也會變差。同時回收臟頁時產生的io壓力對前臺操作時的io性能也會有影響。
因為前臺的內存和io性能都受到了影響,所以前臺平板操作才會變卡頓。
3 一點性能優化心得方面的精彩點評:
這是一個典型的單機系統里面比較特色的內存和io相互作用導致的性能問題。
1) 內存回收時,不得不做很多臟頁回寫工作
因為單機系統比如平板,手機,里面大都是buffer寫,寫到page cache里面就認為工作完成就成功返回了, 雖然省事,但是寫到page cache里面必然會帶來額外的內存消耗。
所以我們看到嵌入式系統里面很多場景下(比如上面的u盤拷貝場景),會造成某些時間段內系統page cache占內存多,其實是里面積累很多臟頁。
pagecache占內存多,系統free內存就會變少。而偏偏系統的write back線程又不活躍(手機ext4文件系統里面開啟延遲寫后,系統每隔30s才回寫一次臟頁),這樣就會造成 [內存分配 → 不得不做內存回收] 這樣路徑下,才會解決pagecache占內存多問題,具體就是觸發做臟頁回寫工作。
2) 問題就在于:
系統的io寫性能不好時,會影響到page cache里面的臟頁釋放。臟頁一旦得不到有效釋放,系統free內存越來越少,這樣內存分配性能也會受到影響。
同時內存分配有問題或者回收效率不高,在處理臟頁回寫方面有缺陷時,也會產生額外的io壓力。所以是內存和io相互影響,相互作用。
所以就有了內核社區每年那個storage and mm國際會議,單機系統里面出現性能問題時,內存和io往往需要同時去優化。
3) 先去優化內存,如果不行,再去優化io
系統的內存和io相互作用導致的性能問題出現時,最好用這種優化思路,原因很簡單,內存優化成本低,風險小。
為什么國內有很多技術文章或者書籍都詳細重點講linux內核內存管理,為什么文件系統和io這塊不去詳細講講,其中有一個重要的原因是:
因為國內企業普遍看到了內存優化成本低,不行就多殺一些進程或者優化下內存分配和回收,即使是內存出問題,頂多重啟一下系統,問題就沒了。
但是io這塊本身技術方面就比較復雜,光內核里面,從vfs到ext4具體文件系統,再到塊設備層,再到存儲bsp層,再到存儲硬件芯片層,這些每個層優化的不好,都會出io性能問題,而且部署io性能優化方案,往往又要對這些每個層都要有所熟悉,這樣才能保證優化副作用降到最小。
這還是討論優化技術本身的,還沒談到,如果存儲io出問題,手機上重要文件損壞,輕則用戶發現文件丟失(這樣的損壞和丟失是你再開機多少次都無法挽回的),重則無法開機事故就出來了。
詳細可以見我的另外一篇技術文檔:《手機Android存儲性能優化架構分析》。
4 解決思路:
在社區高版本內核上面找了一些patch,這些patch是對內核內存回收方面的優化,重點也是優化臟頁過多時,內存回收耗時問題的。
優化1:
1)優化原理
內存回收時,提前喚醒write-back內核線程,提前把所有的臟頁都寫入到磁盤上。這樣交給專門的內核回寫線程來做臟頁回寫工作,這樣效率更高。
因為內存回收路徑中做的臟頁回收是離散寫,具體是調用pageout → 文件系統writepage函數,這樣每回只寫一個page,在lru鏈表上積累的臟頁比較多時,這樣回寫效率并不高,因此也影響了內存回收。
提前喚醒writeback線程后,writeback線程寫會集中寫,具體是以inode為單元,會把每個inode上的臟頁數據通過ext4的writepages函數,寫到磁盤上。
如果寫的臟頁數據量都一樣,集中寫會做io請求合并,減少io請求處理耗時,這樣顯然比內存回收自己離散寫臟頁性能要好多了。
具體性能優化數據可見我的另外一篇文檔:那些年解的疑難性能問題 - ext4碎片整理。
2)patch內容
具體是社區這個patch: mm/vmscan: wake up flushers for legacy cgroups too.
在內存回收必經路徑shrink_inactive_list函數里面,判斷stat.nr_unqueued_dirty == nr_taken的話,說明此時內核的inactive lru list里面積累了大量臟頁,需要喚醒writeback線程去集中大批量地寫一次。
優化2:
1) 優化原理
在內存回收路徑里面盡量少回收臟頁,少觸發io操作,這樣會降低內存回收direct reclaim路徑的耗時,也會間接優化內存分配slow_path的耗時。
同時為了保證多回收內存,增加更多free page, 會多回收些干凈頁。核心工作是:
把active list上更多的page(比如clean page)加入到inactive list里面,這樣雖然會造成比如這些active clean page被回收后,很有可能還要被重新讀入內存。但是這個負作用比起 陷入緩慢的write back臟頁操作不能立刻滿足前臺內存分配需求 要輕得多。
因為存儲芯片讀是比寫要快很多的,所以上面雖然有那個負作用,但是不足為慮。
2) patch內容
具體是社區這2個patch:
mm: vmscan: only write dirty pages that the scanner has seen twice.
在shrink_inactive_list函數里面,如果direct reclaim,則做以下工作:
是待回收頁是臟頁時,進一步判斷如果該頁沒有設置reclaim標記,那么就僅僅設置下relcaim標記,重新放回active list上,而不去回收它。然后等到第2次再碰到該頁時,如果還是臟頁,再去回收它。
mm: vmscan: move dirty pages out of the way until they're flushed
在lru_add_drain里面的pagevec_move_tail_fn函數里面修改,搞成不管該page是否active,都盡可能地把它放到inactive list上去。
這樣會盡量把active list上的page往inactive list上轉移,因為上面patch是在inactive list上碰到臟頁放到active list上的,所以再帶上這個patch,那么最終效果是更多的clean page被搬到了inactive list上去,這樣就會有更多的page被回收掉。
這樣就會充分釋放了內存,接下來的內存分配性能就會得到優化。
二、 內存回收里面的boost watermark優化改造
改造1
1 問題現象
嵌入式設備里面有時候會出現app熱啟動慢,抓trace分析后,是啟動時的io讀性能差,差的原因是很多讀不是從pagecache中讀,而是直接從磁盤上讀。
2 原因分析
有些嵌入式設備內存并不充裕,然后從log中看到,出問題的內核里面都開啟了boost watermark。這個特性一旦被開啟,就會使得內核內存回收變得更加活躍,并且是只回收干凈文件頁的,臟頁和匿名頁都不回收。
這個特性推出的目的本來是為了降低系統內存碎片的(詳見我另外一篇文檔:android內存碎片優化梳理),但是結果在低內存設備上副作用更加明顯,更加大于它的收益,把app啟動時io讀成功的文件頁都給回收了,這樣就會造成系統的整體io讀性能變差。
3 優化思路
低內存嵌入式設備上因為io讀性能差問題嚴重,所以可以關閉該boost watermark優化,高內存設備上保留。
ps:
性能優化有時就是這樣一種折衷,優化有時候會難免有一些副作用,怎么針對具體問題場景,做到衡量評估好收益和副作用的平衡和折衷,是關鍵的。
改造2
1 問題現象
移動設備上(比如手機,平板)相機場景里整機內存壓力大,會造成相機相關進程內存分配性能差,出現相機操作卡頓問題。
2 原因分析
相機某些操作場景下,相機自身進程會不可避免有高峰值內存分配的需求。當陡然切換到這些操作場景時,由于系統沒有做好應對準備,滿足不了相機內存分配需求,就會造成相機內存性能變差。
3 優化思路
相機場景下可以部署下主動內存回收方案,在高峰值內存場景下,可以緩解相機內存分配壓力。
主動內存回收必須快捷高效,能短時間內釋放出大量可用內存出來。這樣可以借鑒下boost watermark的思想,先回收下文件頁,因為文件頁比匿名頁回收要省時多了。
另外一點是相機自身進程的io讀需求比較少,io讀壓力不大,壓力大的是內存和cpu,所以為了短時間內釋放內存壓力,是可以多回收些文件頁的。
同時改造下boost watermark,把它綁到小核上去干活,避免和相機進程爭用cpu。
三、 從系統全局考慮部署高效內存回收方案
1 問題背景
嵌入式系統,諸如手機,平板,前面提過,內存和存儲容量受限,但用戶對它的使用需求卻在日益膨脹,幾乎當成電腦一樣在用。
所以單機系統里面,整機內存壓力是比較大的,而內存方面的優化涉及面廣,拿android系統來說,從app到fwk, 再到native層的glibc庫,再到底層內核,都會有內存優化空間。
內核內存優化比較麻煩耗時些,而且有些內存性能問題,從上層入手優化,反而更加高效快捷些,所以需要從系統全局考慮出發,去優化整機內存。
2 原生android已有的內存回收方案
1)用戶態的lmkd + 內核的內存psi兩者結合,高效殺進程
殺進程是釋放整機內存壓力最好的方式,系統整機free內存很少時,通過殺掉一些手機后臺低優先級進程,可以快速地騰出可用內存,供前臺app使用。
另外基于內存psi感知,這樣可以更靈敏地感知到程序有性能問題時,就去及時殺進程。
2)Lmkd殺進程缺陷
目前的缺陷是,光根據adj來進行殺進程優先級排序還不夠,有些用戶經常使用到的app還是會被頻繁殺掉,這樣會影響到用戶體驗。
另外不區分主進程和子進程,殺掉主進程就會影響到后臺app駐留。
所以還需要在fwk層做些優化工作,因為fwk層最能感知到app業務層的變化,在這里最能根據用戶體驗來部署優化方案。
3)其他的一些優化方法
從性能優化工作角度出發,感覺到目前的linux內核內存管理這塊,更傾向于服務互聯網業務場景。嵌入式單機設備場景比如android手機,想往內核主線分支進性能優化changes,會發現比較困難。
所以現在我們看到的比較新的版本上內核內存回收這塊還是有很多性能問題,有很多待優化空間的。
所以一些諸如高通芯片原廠,在linux內核主線版本上,會打上一些關于嵌入式系統方面的優化(比如process reclaim等),才會交給我們使用。
Linux內核內存回收的一些問題和待優化空間
一、 內存回收目標和收益方面的不確定性
1 內存回收目標
問題1)
direct reclaim時,nr_to_reclaim是它的回收目標,但這個現在固定死了是32個page(詳見__alloc_pages_direct_reclaim → try_to_free_pages里面的 nr_to_reclaim被強制賦值為SWAP_CLUSTER_MAX),
那么如果內存分配只需要1 - 4個page時,陷入到slow path里面做內存回收,客戶只需要回收1 - 4個page就行了,但是內存回收這塊會多回收出額外的31個page出來。額外的回收工作必然會導致回收要多耗時點。
打開底層ftrace,會經常看到前臺操作app時,對應的缺頁異常里面會每次只分配1到4個page,說明在android系統里面分配少量page的需求還是很多的。
原因
這個地方定成32,可能是考慮到系統中有很多進程在做并發direct reclaim的,所以為了權衡系統整體reclaim的壓力和避免更多有用內存頁被回收掉,這個地方就定成了32.
問題2)
內核內存回收direct reclaim的必經函數shrink_node_memcg最下面,完成回收目標后,還要做rebalance the anon lru active/inactive ratio工作,勢必會增加direct reclaim進程的耗時。
另外shrink_inactive_list里面還要做too_many_isolated工作,這個會導致direct reclaim進程睡眠。
原因
可能是為了服務器場景考慮的,為了優化系統全局內存狀態,做一些balance工作。但是嵌入式場景下,該內存回收架構并未區分前臺后,這樣前后臺進程在direct reclaim方面一視同仁,
都要做很多balance系統的工作,這樣的話,會導致前臺app的內存分配耗時增加。
總結:
上面一些問題的發現,說明內存回收這塊目前的架構設計是為服務器場景考慮的,而嵌入式場景,比如手機,比較關注前臺app進程的響應性能的情況下(詳見我的文檔:手機前后臺io分組優化調研),
上面內存回收的一些耗時不確定性問題,至少說明在內存回收這個地方,嵌入式設備尤其是相機場景大內存分配的情況下,還是會有優化空間的。
2 內存回收收益計算
問題
內核在做內存回收過程中,要調用shrink_slab去回收系統的一些緩存。但是內核drivers/staging目錄下面一些緩存,比如ion緩存,就沒有計算這部分緩存實際被回收的page數量。
這樣會造成direct reclaim中,比如回收目標是32個page,本來帶上ion cached部分,就達到回收目標了,不需要再進行新的一輪回收工作了。
但是由于漏統計了ion cached的回收部分,還得再多做一輪回收工作,直到回收夠32個page再結束。
原因
shrink_slab時,實際回收成功的文件系統緩存都可以被內核內存回收模塊統計到,但是ion cached這塊,可能是位于staging分支的緣故,內核主線代碼對它的管理不是很到位而造成的。
二、 rmap查找的耗時
這個是目前內核內存回收中一個比較頭疼的性能問題。
1 問題現象
在android系統里面,用perf工具抓下kswapd的性能profile, 會發現內核內存回收這塊有很多工作花在了 page_referenced -> page_vma_mapped_walk這個地方了。
之前XXX的系統高負載時匿名頁回收性能調查博客里面,也提到了相機app進程會卡在shrink_page_list → page_referenced函數里面時間還比較長。
2 原因分析
1)內核內存回收搞了個二次機會法,增加了rmap反查耗時。
二次機會法意味著所有的內存頁在進入inactive list后,幾乎都要做一遍page_check_references工作,這樣才能被真正回收掉。
這個地方的工作確實有必要,因為lru list只是保證了用戶進程先分配使用的內存頁放到tail,后分配使用的內存頁放到了head.
但是在系統運行過程中,如果放到了inactive list tail的內存頁被進程再次訪問到,那么就會重新變回active,那么就得靠二次機會法來把它移到active list里面。
二次機會法靠這個page_check_references工作,來保證內核內存回收的都是最近不經常被使用到的page.
2) android系統的共享頁很多,也增加了rmap反查耗時。
android里面的app進程都是從zygote進程fork出來的,并且app進程自身也會fork出很多線程。這樣造成了android里面共享頁的確很多,同一個page被共享進程的數目也很多。
這樣就會帶來page_referenced的反查耗時工作。
3) lru active list上也加了page_referenced的反查工作。
這部分反查工作一方面是為了recent_rotated計數,最后是為了get_scan_count里面的確定file list和anon list的查找傾斜度。
高內存壓力場合,get_scan_count里面也推薦用SCAN_EQUAL,而不是SCAN_FRACT,所以這種場合還是否有必要做上面傾斜度確定工作,需要再看看是否能進一步優化下。
另外一方面主要是為了在active page被降級加入到inactive lru list之前,做下清除映射該page的各用戶態進程pte的referenced標記工作。
因為要加入到inactive list上,至少說明此時該page已經是stale page, 已經被降級下來準備要被回收了,所以應該清除下映射該page的pte里面的referenced標記了。
內核內存回收方面的一些梳理:
其實從這些內存回收設計方面可以看出,linux內存回收這塊還是基于一些經驗主義的判斷,不可能做到特別準確地識別出最久不被使用的內存頁。
就是靠不斷地做rmap反查和一些經驗主義判斷(文件頁先放到inactive list上,匿名頁先放到active list,內核認為匿名頁都是比文件頁更活躍的)
來最大程度上保證hot page(即每個進程的working-set內存頁)放到active list上,cold page(進程的非woking set內存頁)放到inactive list上去。
其中在每個page被回收之前,匿名頁一般被反查2次,文件頁一般被反查3次,反查的確比較耗時。
不過linux kernel在性能方面是針對通用系統,重點是為服務器系統設計的,所以嵌入式方面還是有不少特殊場景(比如相機高內存峰值出現場景)可以做內核內存回收方面的性能優化的。
3 優化方法
或者用app compact回收單進程,或者借鑒下lwn上的這篇優化文章:The multi-generational LRU,講的rmap反查的弊端和優化方法。
或者我現在正在調研的思路:相機場景下,高效內存回收思路。
如果要回收的內存數量多,比如回收1 - 2G,那么其實可以識別出相機自身相關進程使用的內存page后,對非相機使用的page可以減少上面rmap的反查工作, 理由如下:
1) 因為非相機進程即使發生page refault,也是僅僅影響的是非相機進程的性能。
2) 在主動內存回收之前,內核的全局lru鏈表中已經大概率是一個比較成熟,比較完美地冷熱內存頁分離的狀況了(hot page放在active list,cold page放在inactive list上),這樣充分利用內核的lru list,先回收冷頁。
3) 最重要此時相機操作需要大量內存,而系統處于內存緊缺狀況,需要短時間內回收出大量內存出來。
而此時結合下面的調研,就算做上面耗時的rmap反查工作,也會很容易出現比如幾秒鐘前才被訪問過的頁面可能都不會被視作活躍,而被回收掉(因為此時回收過程中頁掃描會出現地異常頻繁,會加速頁面老化)。
那還不如不做rmap反查,直接回收算了。
三 、內核匿名頁回收冷熱分離做的還不夠好,導致回收效率低
1匿名內存和文件內存的各自特點
1)匿名內存
內核認為匿名內存大部分都是active的,不會有太多used once或者short-lived的匿名內存。
就是說內核假定:匿名內存,比如malloc,一旦被用戶創建出來分配好對應的物理內存,那么該塊物理內存就會被頻繁使用,如果不被用的話,用戶會及時free該內存的(否則的話,就算是內存泄漏了)。
但是相機場景下,不好說,應該會存在一些并不是被頻繁使用的匿名內存,用戶創建出來后,是為全局考慮的,會出現有一段時間會頻繁使用,使用完并不釋放,而是過一段稍微長時間后,還會繼續用它。
2)文件內存
內核假定文件內存里面有相當一部分是used-once或者short-lived內存(因為文件預讀會引入很多不必要內存頁),當然也有另外一部分是被頻繁使用的內存,兩部分交織在一塊。
2 內核對文件頁和匿名頁在inactive/active分離方面的處理方式
1)匿名內存
因為內核認為匿名內存普通都是hot/active的,只要該塊內存存在,就會被頻繁用到的,所以把新創建的匿名頁加入到了active list上。
同時二次機會法也不適用它,在inactive list上,只要匿名page被用戶進程訪問到了,就會被立刻晉升到active list上去。
同時inactive_list_is_low函數里面也規定了active list長度要大于inactive的,另外重要的是匿名頁也沒有做working-set識別,轉換和保護。
2)文件內存
上面說的其實是文件內存里面都是inactive和active內存頁混合在一起,所以內核認為文件內存不一定都是active的,所以新創建的文件內存就會被加入到inactive list上去,
然后進行進一步地通過二次機會法來進行篩選,把頻繁訪問的,即active內存晉升到active list上去,inactive的則被留下在內存不足時優先被回收掉。
這樣隨著事件的推移,進程的working-set內存頁就會幾乎全部集中在active list,而不會出現used-once/short-lived這種inactive內存跑到active list上,給進程內存頁的冷熱分離帶來困難。
這樣進程的working-set內存頁就會被建立起來,并且不容易被內存不足時回收掉。
另外進程的working-set在特定時間段內是固定的,但是時間長了,肯定會切換到另外一個working-set里面。
在working-set切換時,文件頁有refault distance算法來防止出現切換后,頻繁出現page thrashing.
3 最后結論
文件頁回收有很多優化算法在保證盡量做到冷熱分離和減小page thrashing,但是匿名頁卻沒有這些算法保證,所以匿名頁回收效率感覺比文件頁低,導致的一個不好影響就是匿名頁發生的page thrashing應該比較多于文件頁。
具體僅僅這里是通過代碼分析發現的,還得通過實驗數據驗證。
四、 內核的內存回收從來都是被動的回收
1 具體特點
1) 內存回收都是內存分配不能滿足需求時,才不得不陷入到slow path分配中做回收,這樣對內存分配性能是會有差的影響的。
2) 就算陷入到slow path中,觸發的kswapd線程,也是在balance式地回收,只要內存水位稍微達到一定閥值,內存處于balanced狀態后,就停止工作了。
3) 內存頁面的老化,只有在lru list被scan時,才會老化。也就是說LRU里面的老化時間流逝跟自然時間是沒有關系的。
掃描才是推動歷史車輪前進的動力。而掃描又是由于達不到balanced而被觸發的,可見頁面老化的速度跟系統中內存的緊缺程度是相關的。
內存緊缺的時候,1分鐘前才被訪問過的頁面可能都不會被視作活躍;反過來,如果內存不緊缺,長期不需要進行回收,那么幾小時前訪問過的頁面又可能都會被視作活躍,并且在這段時間內被訪問過一次和被訪問過多次的頁面會被同等對待
看過內存回收二次機會法,才會對這個點有深入理解。
4) 目前的防止出現內存thrashing而做的workingset保護,只是保護了文件頁,匿名頁還未進行保護(好像內核5.9版本上才有)。其實匿名頁也需要進行workingset保護,減小thrashing。
2 導致的問題
移動設備相機場景下,經常有突發性的高峰值內存分配需求出現。如果還是上面這種內存回收特點的話,是會導致相機內存分配性能差,從而出現用戶操作卡頓,殺后臺現象比較嚴重。
3 解決方法
制定相機場景下的殺進程管理策略,還有主動內存回收策略,從而釋放整機內存壓力,優化相機內存分配性能。
審核編輯:劉清
-
嵌入式系統
+關注
關注
41文章
3598瀏覽量
129558 -
cpu
+關注
關注
68文章
10878瀏覽量
212167 -
LINUX內核
+關注
關注
1文章
316瀏覽量
21672
原文標題:使用內存回收技術對嵌入式系統進行性能優化
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論