進程/線程上下文切換會用掉你多少CPU?

進程是操做系統的偉大發明之一,對應用程序屏蔽了CPU調度、內存管理等硬件細節,而抽象出一個進程的概念,讓應用程序專心於實現本身的業務邏輯既可,並且在有限的CPU上能夠「同時」進行許多個任務。可是它爲用戶帶來方便的同時,也引入了一些額外的開銷。以下圖,在進程運行中間的時間裏,雖然CPU也在忙於幹活,可是卻沒有完成任何的用戶工做,這就是進程機制帶來的額外開銷。 php

file

在進程A切換到進程B的過程當中,先保存A進程的上下文,以便於等A恢復運行的時候,可以知道A進程的下一條指令是啥。而後將要運行的B進程的上下文恢復到寄存器中。這個過程被稱爲上下文切換。上下文切換開銷在進程很少、切換不頻繁的應用場景下問題不大。可是如今Linux操做系統被用到了高併發的網絡程序後端服務器。在單機支持成千上萬個用戶請求的時候,這個開銷就得拿出來講道說道了。由於用戶進程在請求Redis、Mysql數據等網絡IO阻塞掉的時候,或者在進程時間片到了,都會引起上下文切換。 nginx

file

一個簡單的進程上下文切換開銷測試實驗

廢話很少說,咱們先用個實驗測試一下,到底一次上下文切換須要多長的CPU時間!實驗方法是建立兩個進程並在它們之間傳送一個令牌。其中一個進程在讀取令牌時就會引發阻塞。另外一個進程發送令牌後等待其返回時也處於阻塞狀態。如此往返傳送必定的次數,而後統計他們的平均單次切換時間開銷。redis

# gcc main.c -o main
# ./main./main
Before Context Switch Time1565352257 s, 774767 us
After Context SWitch Time1565352257 s, 842852 us

每次執行的時間會有差別,屢次運行後平均每次上下文切換耗時3.5us左右。固然了這個數字因機器而異,並且建議在實機上測試。 sql

前面咱們測試系統調用的時候,最低值是200ns。可見,上下文切換開銷要比系統調用的開銷要大。系統調用只是在進程內將用戶態切換到內核態,而後再切回來,而上下文切換但是直接從進程A切換到了進程B。顯然這個上下文切換須要完成的工做量更大。後端

進程上下文切換開銷都有哪些

那麼上下文切換的時候,CPU的開銷都具體有哪些呢?開銷分紅兩種,一種是直接開銷、一種是間接開銷。 緩存

直接開銷就是在切換時,cpu必須作的事情,包括:服務器

  • 一、切換頁表全局目錄
  • 二、切換內核態堆棧
  • 三、切換硬件上下文(進程恢復前,必須裝入寄存器的數據統稱爲硬件上下文)網絡

    • ip(instruction pointer):指向當前執行指令的下一條指令
    • bp(base pointer): 用於存放執行中的函數對應的棧幀的棧底地址
    • sp(stack poinger): 用於存放執行中的函數對應的棧幀的棧頂地址
    • cr3:頁目錄基址寄存器,保存頁目錄表的物理地址
    • ......
  • 四、刷新TLB
  • 五、系統調度器的代碼執行

間接開銷主要指的是雖然切換到一個新進程後,因爲各類緩存並不熱,速度運行會慢一些。若是進程始終都在一個CPU上調度還好一些,若是跨CPU的話,以前熱起來的TLB、L一、L二、L3由於運行的進程已經變了,因此以局部性原理cache起來的代碼、數據也都沒有用了,致使新進程穿透到內存的IO會變多。 其實咱們上面的實驗並無很好地測量到這種狀況,因此實際的上下文切換開銷可能比3.5us要大。 多線程

想了解更詳細操做過程的同窗請參考《深刻理解Linux內核》中的第三章和第九章。併發

一個更爲專業的測試工具-lmbench

lmbench用於評價系統綜合性能的多平臺開源benchmark,可以測試包括文檔讀寫、內存操做、進程建立銷燬開銷、網絡等性能。使用方法簡單,但就是跑有點慢,感興趣的同窗能夠本身試一試。
這個工具的優點是是進行了多組實驗,每組2個進程、8個、16個。每一個進程使用的數據大小也在變,充分模擬cache miss形成的影響。我用他測了一下結果以下:

-------------------------------------------------------------------------
Host                 OS  2p/0K 2p/16K 2p/64K 8p/16K 8p/64K 16p/16K 16p/64K  
                         ctxsw  ctxsw  ctxsw ctxsw  ctxsw   ctxsw   ctxsw  
--------- ------------- ------ ------ ------ ------ ------ ------- -------  
bjzw_46_7 Linux 2.6.32- 2.7800 2.7800 2.7000 4.3800 4.0400 4.75000 5.48000

lmbench顯示的進程上下文切換耗時從2.7us到5.48之間。

線程上下文切換耗時

前面咱們測試了進程上下文切換的開銷,咱們再繼續在Linux測試一下線程。看看究竟比進程能不能快一些,快的話能快多少。

在Linux下其實本並無線程,只是爲了迎合開發者口味,搞了個輕量級進程出來就叫作了線程。輕量級進程和進程同樣,都有本身獨立的task_struct進程描述符,也都有本身獨立的pid。從操做系統視角看,調度上和進程沒有什麼區別,都是在等待隊列的雙向鏈表裏選擇一個task_struct切到運行態而已。只不太輕量級進程和普通進程的區別是能夠共享同一內存地址空間、代碼段、全局變量、同一打開文件集合而已。

同一進程下的線程之全部getpid()看到的pid是同樣的,其實task_struct裏還有一個tgid字段。 對於多線程程序來講,getpid()系統調用獲取的其實是這個tgid,所以隸屬同一進程的多線程看起來PID相同。

咱們用一個實驗來測試一下,其原理和進程測試差很少,建立了20個線程,在線程之間經過管道來傳遞信號。接到信號就喚醒,而後再傳遞信號給下一個線程,本身睡眠。 這個實驗裏單獨考慮了給管道傳遞信號的額外開銷,並在第一步就統計了出來。

# gcc -lpthread main.c -o main
0.508250  
4.363495

每次實驗結果會有一些差別,上面的結果是取了屢次的結果以後而後平均的,大約每次線程切換開銷大約是3.8us左右。從上下文切換的耗時上來看,Linux線程(輕量級進程)其實和進程差異不太大

Linux相關命令

既然咱們知道了上下文切換比較的消耗CPU時間,那麼咱們經過什麼工具能夠查看一下Linux裏究竟在發生多少切換呢?若是上下文切換已經影響到了系統總體性能,咱們有沒有辦法把有問題的進程揪出來,並把它優化掉呢?

# vmstat 1  
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----  
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st  
 2  0      0 595504   5724 190884    0    0   295   297    0    0 14  6 75  0  4  
 5  0      0 593016   5732 193288    0    0     0    92 19889 29104 20  6 67  0  7  
 3  0      0 591292   5732 195476    0    0     0     0 20151 28487 20  6 66  0  8  
 4  0      0 589296   5732 196800    0    0   116   384 19326 27693 20  7 67  0  7  
 4  0      0 586956   5740 199496    0    0   216    24 18321 24018 22  8 62  0  8

或者是

# sar -w 1  
proc/s  
     Total number of tasks created per second.  
cswch/s  
     Total number of context switches per second.  
11:19:20 AM    proc/s   cswch/s  
11:19:21 AM    110.28  23468.22  
11:19:22 AM    128.85  33910.58  
11:19:23 AM     47.52  40733.66  
11:19:24 AM     35.85  30972.64  
11:19:25 AM     47.62  24951.43  
11:19:26 AM     47.52  42950.50  
......

上圖的環境是一臺生產環境機器,配置是8核8G的KVM虛機,環境是在nginx+fpm的,fpm數量爲1000,平均每秒處理的用戶接口請求大約100左右。其中cs列表示的就是在1s內系統發生的上下文切換次數,大約1s切換次數都達到4W次了。粗略估算一下,每核大約每秒須要切換5K次,則1s內須要花將近20ms在上下文切換上。要知道這是虛機,自己在虛擬化上還會有一些額外開銷,並且還要真正消耗CPU在用戶接口邏輯處理、系統調用內核邏輯處理、以及網絡鏈接的處理以及軟中斷,因此20ms的開銷實際上不低了。

那麼進一步,咱們看下究竟是哪些進程致使了頻繁的上下文切換?

# pidstat -w 1  
11:07:56 AM       PID   cswch/s nvcswch/s  Command
11:07:56 AM     32316      4.00      0.00  php-fpm  
11:07:56 AM     32508    160.00     34.00  php-fpm  
11:07:56 AM     32726    131.00      8.00  php-fpm  
......

因爲fpm是同步阻塞的模式,每當請求Redis、Memcache、Mysql的時候就會阻塞致使cswch/s自願上下文切換,而只有時間片到了以後纔會觸發nvcswch/s非自願切換。可見fpm進程大部分的切換都是自願的、非自願的比較少。

若是想查看具體某個進程的上下文切換總狀況,能夠在/proc接口下直接看,不過這個是總值。

grep ctxt /proc/32583/status  
voluntary_ctxt_switches:        573066  
nonvoluntary_ctxt_switches:     89260

本節結論

上下文切換具體作哪些事情咱們沒有必要記,只須要記住一個結論既可,測得做者開發機上下文切換的開銷大約是2.7-5.48us左右,你本身的機器能夠用我提供的代碼或工具進行一番測試。
lmbench相對更準確一些,由於考慮了切換後Cache miss致使的額外開銷。

擴展:平時你們在操做系統理論學習的時候都知道CPU時間片的概念,時間片到了會將進程從CPU上趕下來,換另外一個進程上。但其實在咱們互聯網的網絡IO密集型的應用裏,真正由於時間片到了而發生的非自願切換不多,絕大部分都是由於等待網絡IO而進行的自願切換。 上面的例子你也能夠看出,個人一個fpm進程主動切換有57W次,而被動切換隻有不到9W次。 因此,在同步阻塞的開發模式裏,網絡IO是致使上下文切換頻繁的元兇

file


開發內功修煉之CPU篇專輯:


個人公衆號是「開發內功修煉」,在這裏我不是單純介紹技術理論,也不僅介紹實踐經驗。而是把理論與實踐結合起來,用實踐加深對理論的理解、用理論提升你的技術實踐能力。歡迎你來關注個人公衆號,也請分享給你的好友~~~

相關文章
相關標籤/搜索