Windows/iOS操作流暢而Linux/Android卻很卡頓

今天突然看到這篇文章,寫的太好了,把Linux和windows的調度比較簡單的呈現出來,相當不錯,轉載記錄下來!!!

先說是不是,再問爲什麼。

我就知道有人會這麼說,然而那樣就成了一篇議論文了,而我只是想寫一篇隨筆。所以,不管事實是不是那樣,反正我就是覺得Windows,MacOS,iOS都很流暢,而Linux,Android卻很卡。當然了,這裏說的是GUI,如果考量點換成是Web服務的吞吐和時延,那估計結論要反過來了,不過那是客戶端程序感覺到的事,作爲人,who care!

我寫這篇文章還有一個意思,那就是想牽引一個話題,如果我們想把Linux,Android(當然,Android內核也是Linux)優化到GUI不再卡頓,我們應該怎麼做。

大概是去年,一個炎熱的午後,吃過午飯我和同事們在公司附近晃悠,就討論 “爲什麼蘋果手機就不卡,安卓手機不管多貴都很卡。” 記得一位同事說,iOS在GUI方面做了很多的優化,而Android卻沒有。

這話說對了!不過更爲重要的一點是, 不談具體場景談優化,都是瞎折騰!

Windows也好,iOS也好,都知道自己的應用場景,因此針對自己的應用場景做了優化之後,妥妥在自己拿手的場景下甩Linux在該場景下的表現幾條街了。

下面開始正式的技術層面的分析之前,先聲明幾點。

  • 本文並不是在說Linux系統總體上很卡頓,而只是說Linux系統桌面版的GUI程序相比Winddows很卡頓,如果真覺得本文是在噴Linux,那就當是噴Linux桌面的吧。
  • 本文不準備討論X window和Windows窗口子系統一個在用戶態一個在內核之間的差異,這無關緊要。我的想法是,即便是你將X window扔進內核,現有的Linux內核處理GUI,該卡頓還是卡頓。
  • 本文僅從調度算法的角度來評價爲什麼Windows/iOS不卡頓而Linux卻卡頓,當然還有別的視角,但並不是本文主題。
  • Windows內核調度的線程而不是進程,但是本文統一採用進程這個術語,沒有別的原因,只是因爲進程的概念是和現代操作系統概念相始終的,而線程是後來的概念。

先看服務對象,僅此就將Windows,MacOS/iOS和Linux的使用場景區分開來:

  • Windows/MacOS/iOS系統,主要是被人操作,用來提供寫文檔,遊戲,做報表,畫圖,上網瀏覽,視頻播放等服務。
  • Linux系統,主要提供網絡服務,用來支撐各種遠程的客戶端,爲其提供數據處理和查詢,數據生成,數據存儲等服務。

事實證明,Linux在其專業的領域已經做的足夠好,但是問題是,爲什麼它在GUI處理方面卻總是一直很糟糕呢?這就要看具體場景的差異了。

對於網絡服務而言,其場景的行爲是 可預期的 ,我們可以將這些場景簡單歸結爲:

  • 公平快速處理網絡併發請求。
  • 公平快速處理併發磁盤IO。
  • 高吞吐CPU密集型數據處理與計算。

Linux優秀的O(1)

O(1)調度器以及後來的CFS調度器可以非常完美的cover上述三個場景,至於說爲什麼,不必多說,簡單歸納如下:

  • O(1)的基於優先級的時間片輪轉還是CFS的基於權重的時間配額,均可以既滿足優先級的差別服務需求又保證高吞吐率,這些都來自於調度器本身而不是依靠頻繁的切換。
  • 額外的簡單啓發式獎懲機制可以讓網絡IO以及磁盤IO的響應度更高,同時又不影響CPU密集型計算服務的高吞吐。

上面的第二點是一個額外的輔助,照顧IO過程快速獲得響應,這是一個非常棒的輔助,但是注意,再棒的啓發式算法也總是輔助性的,提高響應度就是個輔助性的錦上添花的功能,以高吞吐爲目標纔是根本。

IO過程對於一臺Linux服務器而言是與外界交互的唯一渠道,通過該渠道可以將處理好的數據送出到網絡或者磁盤,同時從網絡或者磁盤獲取新的數據,換句話說, IO過程類似一道門。 但也僅僅是一道門。

照顧IO過程獲得高響應度這件事是爲了讓門開得更大,通行效率更高!

熟悉Linux內核調度器變遷的都應該知道O(1)
O(1)到CFS過渡的這段歷史,即2.6.0內核開始一直到2.6.22爲止的這些版本,採用Linux內核劃時代的O(1)調度器,隨後由於兩個原因:

  • O(1)調度器動態範圍太大或者太小。
  • IO補償機制不到位,時間片分配不公平。

爲了解決這些問題,Linux內核切換到了CFS調度器。

切換到了CFS調度器,事實上,人們更多指望的是CFS能夠讓進程時間片分配更加公平,多個進程運行更加平滑,如此一來,上GUI界面的話,豈不是就不卡頓了。

然而還是卡頓,本質原因是,場景根本就不對路子。

在Linux服務器的場景中,優先級和時間片是正相關的,無論是O(1)
O(1)調度器的靜態線性映射的時間片,還是CFS的動態時間配額,都是優先級越高的進程其每次運行的時間也就越久,但是實際上,這兩者並不是一回事。

在更復雜的場景中,正確的做法應該是參考 時間管理的四象限法則 來設計進程調度器。其中:

  • 優先級表示緊急性。
  • 時間片表示重要性。

於是,如果不是因爲Linux服務器場景過於單一簡單,CPU的時間管理要複雜得多,比如調度器應該按照四象限法則設計成下面的樣子:

  1. 處理重要且緊急事件的進程,需要賦予高優先級分配長時間片去搶佔當前進程。
  2. 處理重要但是不緊急事件的進程,保持固有優先級分配長時間片就緒等待。
  3. 處理不重要但緊急事件的進程,提升優先級但不分配長時間片,處理完畢立即返回固有優先級。
  4. 既不重要也不緊急的後臺進程,低優先級短時間片,系統閒了再調度。

後面我們會看到,Windows的調度器就是這般設計的。

我們先總體看看GUI系統的場景。

它的服務對象是人,和Linux的服務場景的行爲可預期相反,人的操作是 不可預期 的!

Windows,MacOS/iOS這種Desktop系統的GUI進程,很多時候都是在等待人的進一步操作而睡眠,要麼在等鼠標,要麼在等鍵盤,要麼在等聲卡,顯卡的輸出,或者就是在將用戶輸入的信息往磁盤裏寫而等待IO完成,Desktop系統更多關注的是要對以上這些事件提供高效率的響應服務,而不是系統的數據吞吐。

Desktop在乎的是時延,而不是總吞吐,同時,這個時延還是區分對待的,有些時延的可容忍區間很大,比如網卡(網卡IO之所以優先級提升並不是很多,是因爲首先網卡是有隊列緩存的,而大多數的報文都是burst而來的,隊列緩存可以平滑掉首包延遲,其次,由於光速極限,相比於網絡延遲,主機調度延遲真的可以忽略不計。),有些卻很小,比如鍵盤鼠標。所以說,Windows之類的Desktop系統 必須能夠區分一個進程當前的緊急性和重要性。

Linux內核能做到這種區分嗎?

Linux可以通過計算一個進程的平均睡眠時間判定它是不是一個交互式IO進程,從而決定要不是給它一定的優先級提升,但是也僅能做到這個地步,因爲Linux內核無法得到更進一步的信息。

Linux內核不知道一個進程到底是不是IO進程還是說僅僅在一個時間段內有IO行爲的CPU密集型進程,Linux內核也不知道一個進程被喚醒是因爲鍵盤的數據到了,還是無關緊要的信號到了,所以這一切,Linux內核只能 啓發式預測。

Linux內核僅僅跟蹤一個睡眠時間而且還是平均的睡眠時間,是區別不出進程當前的緊急性和重要性的。沒有外界的信息輸入,僅靠啓發預測,當前的AI算法貌似還沒有到這個境界吧。換句話說,啓發算法是不準確的。你看看Linux內核O(1)
O(1)調度器的sleep_avg是如何計算並如何參與動態優先級調整的,就會明白我上面說的意思。

既然Windows系統的GUI操作比Linux流暢,那麼想必Windows肯定是做到了進程當前的緊急性和重要性的區分咯?那是當然。它是如何做到的呢?

雖然Windows的調度器也是基於優先級的,也是搶佔式的,也是同優先級輪轉的,這看起來和Linux並沒有什麼區別,甚至從4.3BSD開始,幾乎所有的操作系統的調度器基本都是按這個思路設計出來的,僅僅從 如何選出下一個投入運行的進程 這個算法上看,幾乎所有的操作系統調度器都是一樣的。Windows與衆不同的原因在於 其對優先級的不同處理方式。

自4.3BSD以來,所有的基於優先級的搶佔式調度器的優先級計算都包括兩部分因子,即固有優先級和動態優先級:


  

在這裏插入圖片描述

​  

可以看出,Windows對於不同的事件定義了不同的優先級提升的具體數值, 將動態優先級的值和具體的事件做了精確的關聯。

這些數值的定義上,甚至精細而貼心,詳細的數值參見ntddk.h:

//
// Priority increment definitions.  The comment for each definition gives
// the names of the system services that use the definition when satisfying
// a wait.
//

//
// Priority increment used when satisfying a wait on an executive event
// (NtPulseEvent and NtSetEvent)
//

#define EVENT_INCREMENT                 1

//
// Priority increment when no I/O has been done.  This is used by device
// and file system drivers when completing an IRP (IoCompleteRequest).
//

#define IO_NO_INCREMENT                 0


//
// Priority increment for completing CD-ROM I/O.  This is used by CD-ROM device
// and file system drivers when completing an IRP (IoCompleteRequest)
//

#define IO_CD_ROM_INCREMENT             1

//
// Priority increment for completing disk I/O.  This is used by disk device
// and file system drivers when completing an IRP (IoCompleteRequest)
//

#define IO_DISK_INCREMENT               1



//
// Priority increment for completing keyboard I/O.  This is used by keyboard
// device drivers when completing an IRP (IoCompleteRequest)
//

#define IO_KEYBOARD_INCREMENT           6


//
// Priority increment for completing mailslot I/O.  This is used by the mail-
// slot file system driver when completing an IRP (IoCompleteRequest).
//

#define IO_MAILSLOT_INCREMENT           2


//
// Priority increment for completing mouse I/O.  This is used by mouse device
// drivers when completing an IRP (IoCompleteRequest)
//

#define IO_MOUSE_INCREMENT              6


//
// Priority increment for completing named pipe I/O.  This is used by the
// named pipe file system driver when completing an IRP (IoCompleteRequest).
//

#define IO_NAMED_PIPE_INCREMENT         2

//
// Priority increment for completing network I/O.  This is used by network
// device and network file system drivers when completing an IRP
// (IoCompleteRequest).
//
// 網卡IO之所以優先級提升並不是很多,是因爲首先網卡是有隊列緩存的,而大多數的報文都是burst而來的,隊列緩存可以平滑掉首包延遲,其次,由於光速極限,相比於網絡延遲,主機調度延遲真的可以忽略不計。
#define IO_NETWORK_INCREMENT            2


//
// Priority increment for completing parallel I/O.  This is used by parallel
// device drivers when completing an IRP (IoCompleteRequest)
//

#define IO_PARALLEL_INCREMENT           1

//
// Priority increment for completing serial I/O.  This is used by serial device
// drivers when completing an IRP (IoCompleteRequest)
//

#define IO_SERIAL_INCREMENT             2

//
// Priority increment for completing sound I/O.  This is used by sound device
// drivers when completing an IRP (IoCompleteRequest)
//

#define IO_SOUND_INCREMENT              8

//
// Priority increment for completing video I/O.  This is used by video device
// drivers when completing an IRP (IoCompleteRequest)
//

#define IO_VIDEO_INCREMENT              1

//
// Priority increment used when satisfying a wait on an executive semaphore
// (NtReleaseSemaphore)
//

#define SEMAPHORE_INCREMENT             1



仔細看,你會注意到對於聲卡而言,其IO完成時,優先級提升會很大,而磁盤,顯卡這種卻並不是很多,這充分體現了設計者的貼心。這充分考慮到了人耳的靈敏度和人眼的分辨率之間的對比,聲音是作爲流順序輸出的,耳朵很容易分辨出聲音的卡頓,而對於圖像而言,完全可以慢慢雙緩衝刷層,人眼相比之下沒有那麼高的分辨率識別到,因此聲卡事件必須優先處理。同時,對於磁盤,網卡之類的,人就更是感覺不到了。除了聲卡之外,鍵盤鼠標操作的IO完成對於優先級提升的數值也很可觀,因爲鍵盤鼠標如果卡頓,人的輸入會明顯感覺到延遲,鼠標則顯拖沓,這都是很容易識別的卡頓事件,所以Windows給予了進程更高的動態優先級來儘快處理這些事件。

對於窗口子系統而言,當一個窗口獲得焦點時,對應的處理進程的優先級也會得到提升,這會給人一種 你操作的界面總是很流暢 的感覺,畢竟你操作的界面就是前臺窗口,至於說此時後臺窗口的處理進程,即便是僵死了你也不會有感覺,因爲你並不操作它們呀,當你操作它們時,對應的處理進程的優先級就會提升。

所有的優先級提升都伴隨着時間片的重新計算,但是和Linux不同的是,Windows並沒有直接將進程優先級和時間片按照正相關關聯起來,時間片是獨立計算的,大多數時候,Windows對於所有的進程,不管優先級是多少,均採用同一個時間片。

如此看來,Windows雖然也是優先級調度的系統,但是其優先級卻是 操作行爲驅動的 ,這便是其與衆不同之處。

Linux內核調度系統會精細區分磁盤事件的wakeup和鍵盤鼠標聲卡事件的wakeup嗎?不會。

說完了Windows爲什麼操作GUI會很流暢,該說點不好的了,Windows經常會死機,爲什麼呢?

這很大程度上也和上面描述的調度器有關。

仔細看這個操作行爲驅動的動態優先級調度器,很大的一個問題就是容易餓死低優先級的進程,特別是Pbase很低的進程。

Windows的解決方案是採用一個後臺進程(學名叫做平衡集管理線程)輪詢的方式,將超過秒級都沒有被調度的進程的優先級拉昇到很高的位置參與搶佔。

這個機制有啥問題呢?問題在於Windows需要第三方線程來緩解飢餓,而不是靠調度器自身,這便增加了調解失敗的可能:

  • 第三方線程本身的問題沒有按照預期工作。
  • 飢餓進程過多。
  • 飢餓進程優先級提升後又被搶佔。

除了死機問題之外,Windows系統對於服務器版本的調度器調整做的也不夠優雅,Windows僅僅是調整了服務器版本的系統參數,而幾乎沒有對調度的算法做任何修改。對於服務器版本,Windows只是將時間片延長了而已,同時幾乎不再動態計算時間片,而是選擇始終使用相同的一個足夠長的值,以減少進程切換提高吞吐率。顯然這種方式並不妥當,因爲動態優先級根據事件的提升,還是會造成進程間不斷搶佔,從而影響吞吐。

不過,畢竟Windows是一個Desktop系統,本身就不是爲高吞吐而生的,這種針對服務器版本的策略調整便是無可厚非了。正如Linux服務器雖然可以很完美應對高吞吐場景,其Desktop版本比如Ubuntu,Suse不也是心有餘而力不足嗎?雖然Linux內核也有動態優先級提升這一說。

該簡單總結一下了。

在人機關聯上,Windows更加靠近人這一端,適應了人的操作行爲,爲操作該機器的人提供了良好的短時延體驗,Linux相反,它靠近機器一端,讓CPU可以儘可能開足馬力跑task而不是頻繁切換,從而爲客戶端提供最大的數據吞吐。

在這裏插入圖片描述
Windows的設計甚是精妙,考慮到了人的行爲的每一個細節(除了對於死機的耐受力),除了動態優先級和具體時間精確關聯之外,對於待機恢復時間deadline在7秒內也是很值得拍案,這個7秒的閾值考慮到了人的短期記憶的極限,如果有人突然想到了一個點子,需要打開電腦將其記錄下來,那麼打開電腦的時間如果超過了7秒,那麼可能這個點子就溜走了,所以待機恢復時間必須限制在7秒以內,哇塞不哇塞。

對於MacOS/iOS沒有過多的研究,但是可以想見應該也如Windows這般了。因爲它們都處在人機關聯的人的這一端。隨便看了下MacOS的開發手冊,找到了下面的段落:

當我找和GUI和調度相關的東西時,就在上面這段的下面,有這個定義:

在這裏插入圖片描述

嗯,看來內核也是能看到所謂的前臺窗口的。

不管怎麼說,Windows,MacOS/iOS這些系統,共同的特點就是 大多數情況下,同時只有一個焦點窗口在前端接受輸入輸出。 畢竟把窗口縮小排滿一屏幕的很少見。然後呢?然後這就是一個典型的場景啊!

你看看Win10,不就可以設置爲平板模式嗎?

傾其機器和操作系統內核所有資源和機制照顧這少數的,幾乎是唯一的前臺焦點窗口的處理進程,這幾乎就是單進程處理啊! 然後處理好用戶的窗口切換即可,比如Windows的Ctrl-Tab。

Linux如若按照這個思路,單獨再寫一個調度器,替換掉CFS,而不是增加一個調度類,如此一來將系統中所有的進程統一按照 優先級和事件相關聯 的方式對待,我想問題應該能優化不少。

已經快凌晨了,說點別的但是相關的吧。

Linux內核O(1)調度器的歷史其實很短暫,2.6初始到2.6.22,但是非常經典的Linux內核方面的書,都是在描述這期間的Linux內核版本,這在當時就給了人們一個假象,O(1)調度器是無敵的,是劃時代的,於是,當有了新的CFS調度器的時候,人們哇塞一聲,O(1)只是銀河系級別的,而CFS是宇宙級別的。

但其實,O(1)的意義只是優化了 如何快速找到下一個要運行的進程 ,雖然它也涉及了動態優先級的計算,但是這並不是它的重點。說實話,你若看看Windows的調度器,4.4BSD,SystemV4的調度器,基本上都是位圖加優先級隊列的形式,思路幾乎是同一個,這麼說來都是O(1)咯,而且人家這些調度器早在Linux還是O(n)調度器的時候就已經存在好幾年了,卻無人問津。

Windows內核的調度算法不爲人知的原因除了其閉源之外,還有一個原因就是Windows內核方面的技術總體上推廣的人太少,國內除了潘愛民一直在致力於這方面的推廣之外,在沒有別人了。估計是因爲大家覺得Windows內核方面,Debug之外的東西,學了也沒啥用吧。

你說Linux開源沒錯,BSD不也開源嗎?怎麼就沒有人注意BSD的調度器實現呢?哈哈,開不開源無所謂,關鍵得能造勢搞事情,而且獲取方便,讓大家用起來你的東西才真真的啊。Linux2.4版本說實話及其垃圾,但關鍵是很多人用起來了,這就是全部了。Solaris雖然設計完美優雅,可是有壁壘,沒人用,最終也還是涼涼。同樣的事情參考以太網。

通篇都在比較Windows和Linux的調度器如何影響人們的操作體驗。最後說說iOS和Android吧,題外話,不涉及技術。

Android就是卡,不接受反駁。

再貴的Android機器也卡,三星的,華爲的照卡不誤,只是相比別的稍微好一點點而已。這意味着它們成不了街機。因爲手機是買來用的,不是買來debug的,除了程序員沒人在乎Android機慢的原因,即便是程序員也很少有折騰明白的,只是因爲這份職業讓他不用Android就不正確。不過現在互聯網公司的程序員用iPhone的也多了,因爲好用啊。再者說了,互聯網公司程序員大概率以做業務邏輯爲主,底層技術欠缺,無力debug,當然是什麼好用用什麼,iPhone貴,但是互聯網程序員收入高啊。

最終,Android機的唯一優勢就是價格,你讓Android賣的和iPhone一樣貴試試,分分鐘被絞殺。要說還有唯一點五的,就是品牌,三星也不是吃素的,就算三星做的再爛,就憑它這牌子,也不缺市場,比如我就是三星用戶,我並不是覺得三星的Android比小米的Android好,而是我喜歡三星這個公司,這個品牌,僅此而已。
 

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