性能瓶頸--CPU(上下文切換)

CPU的性能瓶頸不僅僅是cpu負載。因爲Linux的性能問題可能是牽一髮而動全身的。
比如一個佔用內存較高的java程序,導致問題的根本原因是內存不足,但是反映最直觀的可能是cpu使用率很高。因爲java開啓了大量的線程進行GC操作。進而導致cpu使用率高,平均負載也隨之升高。所以問題的關鍵還是追根溯源。再比如進程在競爭CPU的時候並沒有真正的運行,但是爲什麼還會導致系統平均負載升高,這就是上下文切換導致的了。

CPU上下文切換

Linux 是一個多任務的操作系統,它支持遠大於CPU數量的任務同時運行,當然並不是真正的同時運行,是每個任務輪流執行CPU分給他們的時間片,讓人感覺是同時在運行。
每一個任務運行前,CPU都需要知道任務從哪裏加載,又從哪裏運行,也就是說,需要系統事先設置好CPU寄存器。
CPU寄存器包含指令寄存器(IR)和程序計數器(PC)。他們用來暫存指令,數據和地址,程序運行的下一條指令地址,這些都是任務運行時的必要環境。因此也被稱作CPU上下文
上下文切換就是把前一個任務的CPU上下文保存起來,然後加載新任務的上下文到這些指令寄存器(IR)和程序寄存器(PC)等寄存器中。這些被保存下來的上下文會存儲在操作系統的內核中,等待任務重新調度執行時再次加載進來,這樣就能保證任務的原來狀態不受影響,讓任務看起來是連續運行的。
根據場景不同,CPU的上下文切換又分爲進程上下文切換,線程上下文切換以及中斷上下文切換。

進程上下文切換

在介紹進程上下文切換前,需要先了解進程執行過程中所涉及到的CPU上下文切換,我們稱之爲特權模式切換
Linux 按照特權等級,將進程的運行空間分爲內核空間和用戶空間。對應的是CPU的環0(Ring 0) 和 環3(Ring3)。(環2和環1,Linux沒用到)

  • 內核空間(Ring0)具有最高權限,可以訪問所有資源。
  • 用戶空間(Ring3)只能訪問受限資源,想要訪問物理設備需要陷入內核態中,在內核空間(Ring3)中,纔可以訪問特權資源。

那麼從用戶態到內核態的轉變就發生一次特權模式切換,如從磁盤上讀取一個文件,就發生了一次內核調用,也就發生一次特權模式切換。CPU需要將寄存器中的用戶態的指令位置保存起來,截至執行內核態的代碼,CPU寄存器需要更新爲內核態的新位置,最後跳轉到內核態執行內核調用。之後再恢復之前的用戶態。這樣的一次系統調用過程實際上發生了兩次CPU上下文切換。
那這就是進程上下文切換嗎?不是的,進程上下文切換隻是說一個進程切換到另一個進程上去。
那特權模式切換和進程上下文切換有什麼區別嗎?
首先進程的管理是有內核進行管理和調度的。進程的切換隻能發生在內核態,所以,進程的上下文切換不僅僅包括了虛擬內存,棧,全局變量等用戶空間資源,還包括了內核態堆棧,寄存器等內核空間狀態。
特別需要注意的是操作系統會將當前任務的虛擬內存一併保存。而Linux中通過TLB來管理虛擬內存到物理內存的映射關係。TLB用於虛擬地址與實地址之間的交互,提供一個尋找實地址的緩存區,能夠有效減少尋找物理地址所消耗時間。當虛擬內存被刷新後,TLB也會被更新。如果沒有TLB,則每次取數據都需要兩次訪問內存,即查頁表獲得物理地址和取數據。在多核的技術下,這會極大的降低程序的執行效率。因爲緩存L3 Cache 是被所有核共享的。當TLB被更新後,緩存中的TLB數據會失效,每個CPU都需要從主存中重新載入,一個進程的上下文切換,同時可能影響其他CPU核心上的進程的執行效率。

TLB 三級緩存.png


當需要進程調度的時候,會需要切換上下文,Linux爲每個CPU維護一個就緒隊列,將活躍的進程按照優先級和等待CPU的時間排序,然後選擇需要CPU的進程(優先級高或者等待時間最長的進程)來運行。
什麼時候會發生進程調度?

 

  • 進程的CPU時間片耗盡,被系統掛起,切換到其他等待CPU的進程運行。
  • 進程所需要的系統資源不足。要等待資源滿足後纔可以運行。這個時候會被系統掛起。
  • 進程通過sleep函數主動將自己掛起。
  • 當有優先級更高的進程運行時,當前進程會被掛起,由高優先級的進程運行。
  • 硬中斷髮生時,CPU上的進程會被掛起,轉而執行內核的中斷服務程序。

線程上下文切換

線程的上下文切換就十分的簡單了,線程是調度的基本單位,而進程是資源擁有的基本單位。

  • 當進程只有一個線程時,進程可理解就是線程。
  • 當進程擁有多個線程時,線程會共享虛擬內存和全局變量等資源,這些資源在上下文切換中不需要修改。
  • 線程的上下文切換也需要保存自己的一些數據,比如棧,寄存器。這些在上下文切換時是需要保存的。
    也就是當
    1、兩個不同進程的線程上下文切換時,此時的切換構成和進程上下文切換一樣。
    2、兩個線程處於同一進程時,切換只需要切換棧,寄存器等少部分資源。

中斷上下文切換

中斷時爲了快速響應硬件事件的,跟進程上下文不同,中斷上下文不涉及進程的用戶態。即便打斷的是一個用戶態的進程,也不需要保存和恢復這個進程的虛擬內存,全局變量等用戶態資源。中斷上下文只包括內核態中斷服務程序執行必需的狀態。CPU寄存器,內核堆棧,硬件中斷參數。

查看系統的上下文切換情況

既然過多的上下文切換會把CPU的時間消耗在上下文環境的保存上,並沒有充分利用其計算功能。那就需要查看當前系統的上下文切換情況了。

vmstat

查看系統的上下文切換情況

 

$ 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
 1  0      0 2453028   2108 1211540    0    0     3    10   27   35  0  0 99  0  0
 0  0      0 2453004   2108 1211572    0    0     0     0   52   66  0  0 100  0  0

  • cs (centext switch) 每秒的上下文切換次數
  • in (interrupt) 每秒的中斷次數
  • r (Runing or Runnable) 就緒隊列的長度,也就是正在運行和等待CPU的進程數。
  • b (Blocked) 不可中斷睡眠狀態的進程數

pidstat

pidstat 可以看到具體的某個應用程序的上下文切換情況。

 

$ pidstat -w 1
Linux 3.10.0-957.el7.x86_64 (localhost.localdomain)     2019年03月23日     _x86_64_    (2 CPU)

13時51分31秒   UID       PID   cswch/s nvcswch/s  Command
13時51分32秒     0         9      9.80      0.00  rcu_sched
13時51分32秒     0        11      0.98      0.00  watchdog/0
13時51分32秒     0        12      0.98      0.00  watchdog/1
13時51分32秒     0       481      0.98      0.00  kworker/1:3
13時51分32秒     0      4683     18.63      0.00  xfsaild/dm-0
13時51分32秒     0      9433      9.80      0.00  vmtoolsd
13時51分32秒     0     27261      0.98      0.00  kworker/u256:0
13時51分32秒     0     40878      2.94      0.00  kworker/0:1
13時51分32秒     0     40880      0.98      0.00  pidstat
  • cswch (voluntary context switches) 自願上下文切換,指的是進程無法獲得所需的資源導致的上下文切換。比如I/O不足,內存不足。
  • nvcswch (non voluntary context switches) 非自願上下文切換,指的是 進程由於時間片已到等原因,被系統強制調度,進而發生上下文切換。比如大量進程在爭搶CPU

模擬上下文切換的場景

這裏使用的sysbench,模擬操作系統的多線程調度瓶頸。以10個線程運行5分鐘的基準測試。模擬多線程切換

 

$  sysbench --threads=100 --max-time=300 threads run

觀察vmstat的輸出

 

 $ 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
17  0      0 2442576   2108 1217720    0    0     3    10   27   80  0  0 99  0  0
11  0      0 2442576   2108 1217720    0    0     0     0 23882 1325686 10 89  1  0  0
10  0      0 2442576   2108 1217720    0    0     0     0 22815 1348568 11 89  0  0  0
17  0      0 2442576   2108 1217720    0    0     0     0 23978 1328481 10 89  1  0  0
11  0      0 2442576   2108 1217720    0    0     0    25 23081 1344558 11 89  0  0  0
14  0      0 2442576   2108 1217720    0    0     0     0 23804 1305193 11 89  1  0  0
19  0      0 2442576   2108 1217720    0    0     0     0 22418 1254798 11 89  0  0  0

可觀察到 cs 瞬間上升(第一行是開機以來的參數的平均值)。
接下來觀察pidstat ,這裏需要加上 -t 參數,顯示線程,要不不帶參數,看不到sysbench。

 

$ pidstat -u -w 1
平均時間:   UID       PID    %usr %system  %guest    %CPU   CPU  Command
平均時間:     0     42213    0.00    0.05    0.00    0.05     -  kworker/0:0
平均時間:     0     42218   19.82  100.00    0.00  100.00     -  sysbench
平均時間:     0     42321    0.33    1.00    0.00    1.33     -  pidstat

平均時間:   UID       PID   cswch/s nvcswch/s  Command
平均時間:     0         2      0.05      0.00  kthreadd
平均時間:     0         3      0.29      0.00  ksoftirqd/0
平均時間:     0         9      8.32      0.00  rcu_sched
平均時間:     0        11      0.24      0.00  watchdog/0
平均時間:     0        12      0.24      0.00  watchdog/1
平均時間:     0        14      0.90      0.00  ksoftirqd/1
平均時間:     0        37      0.10      0.00  khugepaged

加上-t後
可以看到 sysbench 發生了大量的自願上下文切換

 

$ pidstat -wut 1
平均時間:     0     27261         -      1.85      0.00  kworker/u256:0
平均時間:     0         -     27261      1.85      0.00  |__kworker/u256:0
平均時間:   997         -     27300      1.85      0.00  |__grafana-server
平均時間:   997         -     27304      0.93      0.00  |__grafana-server
平均時間:   997         -     27307      1.85      0.00  |__grafana-server
平均時間:   997         -     27341      1.85      0.00  |__grafana-server
平均時間:     0         -     42219   2879.63  11057.41  |__sysbench
平均時間:     0         -     42220   1902.78  13955.56  |__sysbench
平均時間:     0         -     42221   4470.37  11027.78  |__sysbench
平均時間:     0         -     42222   1792.59  17249.07  |__sysbench
平均時間:     0         -     42223   2542.59   7395.37  |__sysbench
平均時間:     0         -     42224   1183.33  16775.93  |__sysbench
平均時間:     0         -     42225   3678.70  12963.89  |__sysbench
平均時間:     0         -     42226   3208.33  15801.85  |__sysbench
平均時間:     0         -     42227   2602.78  13896.30  |__sysbench

通過dstat命令可以看到,除了有很多的資源上下文切換,還有很多中斷。

 

$ dstat
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw 
  1   2  97   0   0   0|5683B   19k|   0     0 |   0     0 | 583    30k
  8  85   7   0   0   0|   0     0 | 132B 1060B|   0     0 |  28k  977k
  8  82  10   0   0   0|   0     0 | 132B  620B|   0     0 |  25k 1025k

這是重調度中斷(RES),在喚醒空閒狀態的CPU來重新調度新任務運行。具體可看 /proc/interrupts。

小結

一般上下文切換在數百到一萬之內上下文切換超過1萬,很可能遇到性能問題。需要具體看看了。

  • 資源上下文切換時說明進程在等待資源,有可能發生了I/O等問題;
  • 非自願上下文切換,說明進程在被強制調度,也就是在爭搶CPU;
  • 中斷次數多了,說明CPU在被中斷處理程序佔用。可以通過/proc/interrupts 查看。



作者:OOM_Killer
鏈接:https://www.jianshu.com/p/1c80cd47e8cf
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

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