1. 程式人生 > >從Java視角理解CPU上下文切換(Context Switch)

從Java視角理解CPU上下文切換(Context Switch)

從Java視角理解系統結構連載, 關注我的微博(連結)瞭解最新動態

在高效能程式設計時,經常接觸到多執行緒. 起初我們的理解是, 多個執行緒並行地執行總比單個執行緒要快, 就像多個人一起幹活總比一個人幹要快. 然而實際情況是, 多執行緒之間需要競爭IO裝置, 或者競爭鎖資源,導致往往執行速度還不如單個執行緒. 在這裡有一個經常提及的概念就是: 上下文切換(Context Switch).

上下文切換的精確定義可以參考: http://www.linfo.org/context_switch.html. 下面做個簡單的介紹. 多工系統往往需要同時執行多道作業.作業數往往大於機器的CPU數, 然而一顆CPU同時只能執行一項任務, 如何讓使用者感覺這些任務正在同時進行呢? 作業系統的設計者巧妙地利用了時間片輪轉的方式, CPU給每個任務都服務一定的時間, 然後把當前任務的狀態儲存下來, 在載入下一任務的狀態後, 繼續服務下一任務. 任務的狀態儲存及再載入, 這段過程就叫做上下文切換. 時間片輪轉的方式使多個任務在同一顆CPU上執行變成了可能, 但同時也帶來了儲存現場和載入現場的直接消耗.
(Note. 更精確地說, 上下文切換會帶來直接和間接兩種因素影響程式效能的消耗. 直接消耗包括: CPU暫存器需要儲存和載入, 系統排程器的程式碼需要執行, TLB例項需要重新載入, CPU 的pipeline需要刷掉; 間接消耗指的是多核的cache之間得共享資料, 間接消耗對於程式的影響要看執行緒工作區操作資料的大小).


在linux中可以使用vmstat觀察上下文切換的次數. 執行命令如下:
Shell程式碼  收藏程式碼
  1. $ vmstat 1
  2. procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----  
  3.  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa  
  4.  100459394445356011181920014122383061921
  5.  0004593212453568111881600096958110841942
  6.  0004593360453568
    11184560000895104431950
  7.  100459340845356811184560000929107341950
  8.  0004593496453568111845600001133136361930
  9.  000459356845356811184760000992119041950

vmstat 1指每秒統計一次, 其中cs列就是指上下文切換的數目. 一般情況下, 空閒系統的上下文切換每秒大概在1500以下.

對於我們經常使用的搶佔式作業系統來說, 引起上下文切換的原因大概有以下幾種: 1. 當前執行任務的時間片用完之後, 系統CPU正常排程下一個任務 2. 當前執行任務碰到IO阻塞, 排程器將掛起此任務, 繼續下一任務 3. 多個任務搶佔鎖資源, 當前任務沒有搶到,被排程器掛起, 繼續下一任務 4. 使用者程式碼掛起當前任務, 讓出CPU時間 5. 硬體中斷. 前段時間發現有人在使用futex的WAIT和WAKE來測試context switch的直接消耗(
連結
), 也有人使用阻塞IO來測試context switch的消耗(連結).那麼Java程式怎麼測試和觀察上下文切換的消耗呢?

我做了一個小實驗, 程式碼很簡單, 有兩個工作執行緒. 開始時,第一個執行緒掛起自己; 第二個執行緒喚醒第一個執行緒,再掛起自己; 第一個執行緒醒來之後喚醒第二個執行緒, 再掛起自己. 就這樣一來一往,互相喚醒對方, 掛起自己. 程式碼如下:
Java程式碼  收藏程式碼
  1. import java.util.concurrent.atomic.AtomicReference;  
  2. import java.util.concurrent.locks.LockSupport;  
  3. publicfinalclass ContextSwitchTest {  
  4.     staticfinalint RUNS = 3;  
  5.     staticfinalint ITERATES = 1000000;  
  6.     static AtomicReference turn = new AtomicReference();  
  7.     staticfinalclass WorkerThread extends Thread {  
  8.         volatile Thread other;  
  9.         volatileint nparks;  
  10.         publicvoid run() {  
  11.             final AtomicReference t = turn;  
  12.             final Thread other = this.other;  
  13.             if (turn == null || other == null)  
  14.                 thrownew NullPointerException();  
  15.             int p = 0;  
  16.             for (int i = 0; i < ITERATES; ++i) {  
  17.                 while (!t.compareAndSet(other, this)) {  
  18.                     LockSupport.park();  
  19.                     ++p;  
  20.                 }  
  21.                 LockSupport.unpark(other);  
  22.             }  
  23.             LockSupport.unpark(other);  
  24.             nparks = p;  
  25.             System.out.println("parks: " + p);  
  26.         }  
  27.     }  
  28.     staticvoid test() throws Exception {  
  29.         WorkerThread a = new WorkerThread();  
  30.         WorkerThread b = new WorkerThread();  
  31.         a.other = b;  
  32.         b.other = a;  
  33.         turn.set(a);  
  34.         long startTime = System.nanoTime();  
  35.         a.start();  
  36.         b.start();  
  37.         a.join();  
  38.         b.join();  
  39.         long endTime = System.nanoTime();  
  40.         int parkNum = a.nparks + b.nparks;  
  41.         System.out.println("Average time: " + ((endTime - startTime) / parkNum)  
  42.                 + "ns");  
  43.     }  
  44.     publicstaticvoid main(String[] args) throws Exception {  
  45.         for (int i = 0; i < RUNS; i++) {  
  46.             test();  
  47.         }  
  48.     }  
  49. }  

編譯後,在我自己的筆記本上( Intel(R) Core(TM) i5 CPU M 460  @ 2.53GHz, 2 core, 3M L3 Cache) 用測試幾輪,結果如下:
Shell程式碼  收藏程式碼
  1. java -cp . ContextSwitchTest  
  2. parks: 953495
  3. parks: 953485
  4. Average time: 11373ns  
  5. parks: 936305
  6. parks: 936302
  7. Average time: 11975ns  
  8. parks: 965563
  9. parks: 965560
  10. Average time: 13261ns  
我們會發現這麼簡單的for迴圈, 線性執行會非常快,不需要1秒, 而執行這段程式需要幾十秒的耗時. 每個上下文切換需要耗去十幾us的時間,這對於程式吞吐量的影響很大.

同時我們可以執行vmstat 1 觀查一下上下文切換的頻率是否變快
Shell程式碼  收藏程式碼
  1. $ vmstat 1
  2. procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----  
  3.  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa  
  4.  100442498845796411549120013122528061921
  5.  0004420452457964115990000001586206961930
  6.  1004407676457964117155200001436188383890
  7.  1004402916457964117203200084229824579294852
  8.  100441602445796411589120000953821985441710730
  9.  1104416096457964115896800011679973159934187740
  10.  100442038445796411547760000962651960761510741
  11.  100440301245797211710960001521043212135372012662


再使用strace觀察以上程式中Unsafe.park()究竟是哪道系統呼叫造成了上下文切換:
Shell程式碼  收藏程式碼
  1. $strace -f java -cp . ContextSwitchTest  
  2. [pid  5969] futex(0x9571a9c, FUTEX_WAKE_OP_PRIVATE, 110x9571a98, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
  3. [pid  5968] <... futex resumed> )       = 0
  4. [pid  5969] futex(0x9571ad4, FUTEX_WAIT_PRIVATE, 949, NULL <unfinished ...>  
  5. [pid  5968] futex(0x9564368, FUTEX_WAKE_PRIVATE, 1) = 0
  6. [pid  5968] futex(0x9571ad4, FUTEX_WAKE_OP_PRIVATE, 110x9571ad0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1} <unfinished ...>  
  7. [pid  5969] <... futex resumed> )       = 0
  8. [pid  5968] <... futex resumed> )       = 1
  9. [pid  5969] futex(0x9571628, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>  
果然還是futex.

再使用perf看看上下文對於Cache的影響:
Shell程式碼  收藏程式碼
  1. $ perf stat -e cache-misses   java -cp . ContextSwitchTest  
  2. parks: 999999
  3. parks: 1000000
  4. Average time: 16201ns  
  5. parks: 998930
  6. parks: 998926
  7. Average time: 14426ns  
  8. parks: 998034
  9. parks: 998204
  10. Average time: 14489ns  
  11.  Performance counter stats for 'java -cp . ContextSwitchTest':  
  12.          2,550,605 cache-misses                                                  
  13.       90.221827008 seconds time elapsed  
1分半鐘內有255萬多次cache未命中.

嗯, 貌似太長了, 可以結束了. 接下來會繼續幾篇博文繼續分析一些有意思的東西.
(1) 從Java視角看記憶體屏障 (Memory Barrier)
(2) 從java視角看CPU親緣性 (CPU Affinity)
等..敬請關注


PS. 其實還做了一個實驗, 測試CPU Affinity對於Context Switch的影響.
Shell程式碼  收藏程式碼
  1. $ taskset -c 0 java -cp . ContextSwitchTest  
  2. parks: 992713
  3. parks: 1000000
  4. Average time: 2169ns  
  5. parks: 978428
  6. parks: 1000000
  7. Average time: 2196ns  
  8. parks: 989897
  9. parks: 1000000
  10. Average time: 2214ns  
這個命令把程序繫結在0號CPU上,結果Context Switch的消耗小了一個數量級, 什麼原因呢? 賣個關子, 在談到CPU Affinity的博文再說

相關推薦

Java視角理解CPU上下文切換(Context Switch)

從Java視角理解系統結構連載, 關注我的微博(連結)瞭解最新動態 在高效能程式設計時,經常接觸到多執行緒. 起初我們的理解是, 多個執行緒並行地執行總比單個執行緒要快, 就像多個人一起幹活總比一個人幹要快. 然而實際情況是, 多執行緒之間需要競爭IO裝置, 或者競爭鎖資源

Java視角理解系統結構(一)CPU上下文切換

作者:Minzhou  本文是從Java視角理解系統結構連載文章 在高效能程式設計時,經常接觸到多執行緒. 起初我們的理解是, 多個執行緒並行地執行總比單個執行緒要快, 就像多個人一起幹活總比一個人幹要快. 然而實際情況是, 多執行緒之間需要競爭IO裝置, 或者競爭鎖資源,導致往往執行速度還不

Java視角理解系統結構(二)CPU快取

從Java視角理解系統結構連載, 關注我的微博(連結)瞭解最新動態 眾所周知, CPU是計算機的大腦, 它負責執行程式的指令; 記憶體負責存資料, 包括程式自身資料. 同樣大家都知道, 記憶體比CPU慢很多. 其實在30年前, CPU的頻率和記憶體匯流排的頻率在同一個級別, 訪問記憶體只比訪問

Java視角理解系統結構(三)偽共享

從Java視角理解系統結構連載, 關注我的微博(連結)瞭解最新動態 從我的前一篇博文中, 我們知道了CPU快取及快取行的概念, 同時用一個例子說明了編寫單執行緒Java程式碼時應該注意的問題. 下面我們討論更為複雜, 而且更符合現實情況的多核程式設計時將會碰到的問題. 這些問題更容易犯, 連j

Java視角理解偽共享(False Sharing)

從Java視角理解系統結構連載, 關注我的微博([url="http://weibo.com/coderplay"]連結[/url])瞭解最新動態從我的[url="http://coderplay.iteye.com/blog/1485760"]前一篇博文[/url]中, 我

效能測試必備知識(5)- 深入理解CPU 上下文切換

做效能測試的必備知識系列,可以看下面連結的文章哦 https://www.cnblogs.com/poloyy/category/1806772.html   前言 上一篇文章中,舉例了大量程序等待 CPU 排程的場景   靈魂拷問 既然程序是在等待,並沒有執行,為什麼系統的平均負載還是會

CPU上下文切換的次數和時間(context switch

分享 2.6 輸出 -c tar dsta make legacy RR 什麽是CPU上下文切換? 現在linux是大多基於搶占式,CPU給每個任務一定的服務時間,當時間片輪轉的時候,需要把當前狀態保存下來,同時加載下一個任務,這個過程叫做上下文切換。時間片

深入理解Linux的CPU上下文切換

如何理解Linux的上下文切換 Linux 是一個多工作業系統,它支援同時執行的任務數量遠大於 CPU 個數。其實這些任務沒有真正的同時執行,是因為系統在很短的時間內,將 CPU 輪流分配給它們,造成多工同時執行的錯覺。 而在每個任務執行前,CPU 都需要知道任務從哪裡載入、從哪裡開始執

lmbench的使用方法 與CPU上下文切換的次數和時間(context switch

一、引言 要評價一個系統的效能,通常有不同的指標,相應的會有不同的測試方法和測試工具,一般來說為了確保測試結果的公平和權威性,會選用比較成熟的商業測試軟體。但在特定情形下,只是想要簡單比較不同系統或比較一些函式庫效能時,也能夠從開源世界裡選用一些優秀的工具來完成這個

flask視角理解angular(三)ORM VS Service

不同 style 實現 component con 如何 怎麽辦 mode string 把獲取模型數據的任務重構為一個單獨的服務,它將提供英雄數據,並把服務在所有需要英雄數據的組件間共享。 @Injectable() export class HeroServic

flask視角理解angular(二)Blueprint VS Component

location class 表示 one camel 區別 標準 wak void Component類似flask app下面的每個blueprint。 import ‘rxjs/add/operator/switchMap‘; import { Co

cpu上下文切換

cpu上下文就是暫存器和程式計數器。這裡記錄著指令的位置,他們存在系統核心 系統呼叫過程叫上下文切換。 程序的上下文切換,執行緒的上下文切換,中斷的上下文切換 一:程序的上下文切換:他與系統呼叫的不同是,程序中還包括,虛擬記憶體,全部變數,棧等使用者態。也包括暫存器,核心堆疊等核心態 二:執行緒的上下

cpu上下文切換(下)

--怎麼檢視系統的上下文切換情況 過多的上下文切換,會把cpu時間消耗在暫存器、核心棧以及虛擬記憶體等資料的儲存和恢復上,縮短程序真正執行的時間,成了系統性能大幅下降的一個元凶。 檢視,使用vmstat,來檢視系統的上下文切換 -vmstat是一個常用的系統性能分析工具,主要用來分析系統的記憶體使用情況

一文讓你明白CPU上下文切換

我們都知道,Linux 是一個多工作業系統,它支援遠大於 CPU 數量的任務同時執行。當然,這些任務實際上並不是真的在同時執行,而是因為系統在很短的時間內,將 CPU 輪流分配給它們,造成多工同時執行的錯覺。 而在每個任務執行前,CPU 都需要知道任務從哪裡載入、又從哪裡開始執行,也

CPU上下文切換分析

一、CPU上下文切換 1、上下文切換,有時也稱做程序切換或任務切換,是指CPU從一個程序或執行緒切換到另一個程序或執行緒。 2、vmstat是一個常用的系統性能分析工具,主要用來分析系統記憶體使用情況,也常用來分析CPU上下文切換和中斷的次數。 例:vmstat -w 上下文切換需要特別關注的四列

作業系統CPU上下文切換

程序切換 進行程序切換就是從正在執行的程序中收回處理器,然後再使待執行程序來佔用處理器。 這裡所說的從某個程序收回處理器,實質上就是把程序存放在處理器 的暫存器中的中間資料找個地方存起來,從而把處理器的暫存器騰出來讓其他程序使用。那麼被中止執行程序的中間資料存在何處好呢?當然這個地方應該是程序的 私有堆疊。

Linux CPU 上下文切換

system in 每秒CPU的中斷次數,包括時間中斷 cs 每秒上下文切換次數,例如我們呼叫系統函式,就要進行上下文切換,執行緒的切換,也要程序上下文切換,這個值要越小越好,太大了,要考慮調低執行緒或者程序的數目,例如 在apache和nginx這種web伺服器中

效能測試必備知識(6)- 如何檢視“CPU 上下文切換

做效能測試的必備知識系列,可以看下面連結的文章哦 https://www.cnblogs.com/poloyy/category/1806772.html   課前準備,安裝 sysbench 下載 sysbench git clone https://github.com/akopytov/sy

零開始理解JAVA事件處理機制(2)

extend nds 接下來 htm ref param 簡單 tostring ansi 第一節中的示例過於簡單《從零開始理解JAVA事件處理機制(1)》,簡單到讓大家覺得這樣的代碼簡直毫無用處。但是沒辦法,我們要繼續寫這毫無用處的代碼,然後引出下一階段真正有益的代碼。

Java多執行緒的上下文切換

轉載自 https://blog.csdn.net/fuyuwei2015/article/details/71860349 對於上下文切換不同的作業系統模式也不盡相同,這裡我們只討論Unix系統,在我之前的文章中提到過windows的搶佔式,這裡就不在贅述。  無論是單核還