爲什麼Linux CFS調度器沒有帶來驚豔的碾壓效果?

但凡懂Linux內核的,都知道Linux內核的CFS進程調度算法,無論是從2.6.23將其初引入時的論文,還是各類源碼分析,文章,以及Linux內核專門的圖書,都給人這樣一種感覺,即 CFS調度器是革命性的,它將徹底改變進程調度算法。 預期中,人們期待它會帶來令人驚豔的效果。

然而這是錯覺。

人們希望CFS速勝,但是分析來分析去,卻只是在某些方面比O(1)調度器稍微好一點點。甚至在某些方面比不上古老的4.4BSD調度器。可是人們卻依然對其趨之若鶩,特別是源碼分析,汗牛塞屋!

爲什麼CFS對別的調度算法沒有帶來碾壓的效果呢?

首先,在真實世界,碾壓是不存在的,人與人,事與事既然被放在了同一個重量級梯隊比較,其之間的差別沒有想象的那麼大,根本就不在誰碾壓誰。不能被小說電視劇電影矇蔽了,此外,徐曉冬大擺拳暴打雷雷也不算數,因爲他們本就不是一個梯隊。

任何領域,革命性的碾壓式推陳出新並不是沒有,但是概率極低,人們普遍的狂妄在於,總是認爲自己所置身的環境正在發生着某種碾壓式的變革,但其實,最終大概率不過是一場平庸。

任何領域,革命性的碾壓式推陳出新並不是沒有,但是概率極低,人們普遍的狂妄在於,總是認爲自己所置身的環境正在發生着某種碾壓式的變革,但其實,最終大概率不過是一場平庸。

最終就出現了角力,僵持。

其次,我們應該看到,CFS調度器聲稱它會給交互式進程帶來福音,在這方面CFS確實比O(1)做得好,但是驚豔的效果來自於粉絲的認同。Linux系統交互進程本來就不多,Linux更多地被裝在服務器,而在服務器看來,吞吐是要比交互響應更加重要的。

那麼以交互爲主的Android系統呢?我們知道,Android也是採用了CFS調度器,也有一些事BFS,爲什麼同樣沒有帶來驚豔的效果呢?

我承認,2008年前後出現CFS時還沒有Android,等到Android出現時,其採用的Linux內核已經默認了CFS調度器,我們看下Android版本,Linux內核版本以及發行時間的關係:

Linux內核在2.6.23就採用了CFS調度器。所以一個原因就是沒有比較。Android系統上,CFS沒有機會和O(1)做比較。

另外,即便回移一個O(1)調度器到Android系統去和CFS做AB,在我看來,CFS同樣不會驚豔,原因很簡單,Android系統幾乎都是交互進程,卻前臺進程永遠只有一個,你幾乎感受不到進程的切換卡頓,換句話說,即便CFS對待交互式進程比O(1)好太多,你也感受不到,因爲對於手機,平板而言,你切換APP的時間遠遠大於進程切換的時間粒度。

那麼,CFS到底好在哪裏?

簡單點說,CFS的意義在於, 在一個混雜着大量計算型進程和IO交互進程的系統中,CFS調度器對待IO交互進程要比O(1)調度器更加友善和公平。理解這一點至關重要。

其實,CFS調度器的理念非常古老,就說在業界,CFS的思想早就被應用在了磁盤IO調度,數據包調度等領域,甚至最最古老的SRV3以及4.3BSD UNIX系統的進程調度中早就有了CFS的身影,可以說,Linux只是使用CFS調度器,而不是設計了CFS調度器

就以4.3BSD調度器爲例,我們看一下其調度原理。

4.3BSD採用了1秒搶佔制,每間隔1秒,會對整個系統進程進行優先級排序,然後找到優先級最高的投入運行,非常簡單的一個思想,現在看看它是如何計算優先級的。

首先,每一個進程j均擁有一個CPU滴答的度量值Cj,每一個時鐘滴答,當前在運行的進程的CPU度量值C會遞增:

當一個1秒的時間區間ii過去之後,Cj被重置,該進程jj的優先級採用下面的公式計算:

可以計算,在一個足夠長的時間段內,兩個進程運行的總時間比例,將和它們的Base_PrioBase_Prio優先級的比例相等。

4.3BSD的優先級公平調度是CPU滴答驅動的。

現在看Linux的CFS,CFS採用隨時搶佔制。每一個進程j均攜帶一個 虛擬時鐘VCj,每一個時鐘滴答,當前進程k的VCk會重新計算,同時調度器選擇VC最小的進程運行,計算方法非常簡單:

可見, Linux的CFS簡直就是4.3BSD進程調度的自驅無級變速版本!

如果你想了解CFS的精髓,上面的就是了。換成語言描述,CFS的精髓就是 “n個進程的系統,任意長的時間週期TT,每一個進程運行T/n的時間!

當然,在現實和實現中,會有80%的代碼處理20%的剩餘問題,比如如何獎勵睡眠太久的進程等等,但是這些都不是精髓。

綜上,我們總結了:

  • 現實世界很難碾壓同級別的人或事。

  • 大量的Linux服務器不需要照顧交互進程,CFS優勢無法凸顯。

  • 大量的Android系統沒有和O(1)同臺競技的機會。

  • 大量的Android系統交互進程很難感知進程調度這件事。

  • CFS調度思想古已有之。

所以無論從概念還是從效果,Linux CFS調度器均沒有帶來令人眼前一亮的哇塞效果。但是還缺點什麼。嗯,技術上的解釋。

分析和解釋任何一個機制之前,必然要先問,這個機制的目標是什麼,它要解決什麼問題,這樣纔有意義。而不能僅僅是明白了它是怎麼工作的。

那麼Linux CFS調度器被採用,它的目標是解決什麼問題的呢?它肯定是針對O(1)算法的一個問題而被引入並取代O(1),該問題也許並非什麼臭名昭著,但是確實是一枚釘子,必須拔除。

O(1)調度器的本質問題在於 進程的優先級和進程可運行的時間片進行了強映射!

也就是說,給定一個進程優先級,就會計算出一個時間片與之對應,我們忽略獎懲相關的動態優先級,看一下原始O(1)算法中一個進程時間片的計算:

#define BASE_TIMESLICE(p) (MIN_TIMESLICE + /
((MAX_TIMESLICE - MIN_TIMESLICE) * /
(MAX_PRIO-1 - (p)->static_prio) / (MAX_USER_PRIO-1)))

static inline unsigned int task_timeslice(task_t *p)
{
    return BASE_TIMESLICE(p);
}

直觀點顯示:

針對上述問題,2.6內核的O(1)O(1)引入了雙斜率來解決:

static unsigned int task_timeslice(task_t *p)
{
    if (p->static_prio < NICE_TO_PRIO(0))
        return SCALE_PRIO(DEF_TIMESLICE*4, p->static_prio);
    else
        return SCALE_PRIO(DEF_TIMESLICE, p->static_prio);
}

直觀圖示如下:

貌似問題解決了,但是如果單單揪住上圖的某一個優先級子區間來看,還是會有問題,這就是相對優先級的問題。我們看到,高優先級的時間片是緩慢增減的,而低優先級的時間片卻是陡然增減,同樣都是相差同樣優先級的進程,其優先級分佈影響了它們的時間片分配。

本來是治瘸子,結果腿好了,但是胳臂壞了。

本質上來講,這都源自於下面兩個原因:

固定的優先級映射到固定的時間片。

相對優先級和絕對優先級混雜。

那麼這個問題如何解決?

優先級和時間片本來就是兩個概念,二者中間還得有個變量溝通纔可以。優先級高只是說明該進程能運行的久一些,但是到底久多少,並不是僅僅優先級就能決定的,還要綜合考慮,換句話距離來說,如果只有一個進程,那麼即便它優先級再低,它也可以永久運行,如果系統中有很多的進程,即便再高優先級的進程也要讓出一些時間給其它進程。

所以,考慮到系統中總體的進程情況,將優先級轉換爲權重,將時間片轉換爲份額,CFS就是了。最終的座標系應該是權重佔比/時間片座標系而不是權重(或者優先級)/時間片。應該是這個平滑的樣子:

看來,Linux CFS只是爲了解決O(1)O(1)中一個“靜態優先級/時間片映射”問題的,那麼可想而知,它又能帶來什麼驚豔效果呢?這裏還有個“但是”,這個O(1)O(1)調度器的問題其實在計算密集型的守護進程看來,並不是問題,反而是好事,畢竟高優先級進程可以無條件持續運行很久而不切換。這對於吞吐率的提高,cache利用都是有好處的。無非也就侵擾了交互進程唄,又有何妨。

當然,使用調優CFS的時候,難免也要遇到IO睡眠獎懲等剩餘的事情去設計一些trick算法,這破費精力。

對了,還要設置你的內核爲HZ1000哦,這樣更能體現CFS的平滑性,就像它宣稱的那樣。我難以想象,出了Ubuntu,Suse等花哨的桌面發行版之外,還有哪個Linux需要打開HZ1000,服務器用HZ250不挺好嗎?

關於調度的話題基本就說完了,但是在進入下一步固有的噴子環節之前,還有兩點要強調:

  1. CFS的時間片是動態的,是系統負載均衡以及其優先級的函數,這便可以把進程調度動態適應到系統最佳,以節省切換開銷。

  2. 即便是到了多核時代,對於實時進程依然像單核時代那般嚴格遵循最優先調度。

我還是想說,在調度器設計方面,大部分的人們關注點錯了!

在CPU核數越來越多的時代,人們更應該關心把進程調度到哪裏CPU核上而不是某個CPU核要運行哪個進程

單核時代一路走過來的Linux,發展迅猛,這無可厚非,但是成就一個操作系統內核的並不單單是技術,還有別的。這些當然程序員們很不愛聽,程序員最煩非技術方面的東西了,程序員跟誰都比寫代碼,程序員特別喜歡噴領導不會寫代碼云云。

Linux在純技術方面並不優秀,Linux總體上優秀的原因是因爲有一羣非代碼不明志的程序員在讓它變得越來越優秀,另一方面還要歸功於開源和社區。Linux的學習門檻極低,如果一個公司能不費吹灰之力招聘到一個Linux程序員的話,那它幹嘛還要費勁九牛二虎之力去招聘什麼高端的BSD程序員呢?最終的結果就是,Linux用的人極多,想換也換不掉了。

但無論如何也沒法彌補Linux內核上的一些原則性錯誤。

Linux內核還是以原始的主線爲base,以講Linux內核的書爲例,經典的Robert Love的《Linux內核設計與實現》,以及《深入理解Linux內核》,在講進程調度的時候,關於多核負載均衡的筆墨都是少之又少甚至沒有,如此經典的著作把很多同好引向了那萬劫不復的代碼深淵。於是乎,鋪天蓋地的CFS源碼分析紛至沓來。

但其實,拋開這麼一個再普通不過的Linux內核,現代操作系統進入了多核時代,其核心正是在cache利用上的革新,帶來的轉變就是進程調度和內存管理的革新。review一下Linux內核源碼,這些改變早就已經表現了出來。

可悲的是,關於Linux內核的經典書籍卻再也沒有更新,所有的從傳統學校出來的喜歡看書學習的,依然是抱着10年前的大部頭在啃。

當然了,Linux內核作爲一個代碼來講,它是普適的,所以社區很難看到且關注單單是多核的問題,社區關注的最多的是可維護性,而不是性能。Linux新特性在128MB內存的i386機器上跑沒有問題,那就是OK的。只要不是80%以上的人遭遇的新問題,社區是從不care的,同時,正因爲如此,社區還會引入bug,這也是令人想嘆息都不能嘆息。

我的看法吧,社區只是一個一切以代碼爲準繩的程序員社區,社區不會過於關注體系結構的發展和新特性,這些都是廠商的事情。

回到進程調度的話題,正因爲Linux一直在關注調度算法本身以及其實現的代碼,纔會出現The Linux Scheduler: a Decade of Wasted Cores,這篇十分中肯的paper:

http://www.ece.ubc.ca/~sasha/papers/eurosys16-final29.pdf

同樣,我一向噴的TCP也是如此,人們關注TCP的實現代碼本身,纔會讓它越來越複雜,然後越來越脆弱,也許你會說這就是進化,但是趁着萬劫不復前,不是還有回爐的機會嗎?還沒有進化到必須繼續進化的地步吧。如果站在外面看且具有強制措施,估計早就沒有垃圾TCP了吧。

浙江溫州皮鞋溼,下雨進水不會胖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章