從Java視角理解CPU上下文切換(Context Switch)
在高效能程式設計時,經常接觸到多執行緒. 起初我們的理解是, 多個執行緒並行地執行總比單個執行緒要快, 就像多個人一起幹活總比一個人幹要快. 然而實際情況是, 多執行緒之間需要競爭IO裝置, 或者競爭鎖資源,導致往往執行速度還不如單個執行緒. 在這裡有一個經常提及的概念就是: 上下文切換(Context Switch).
上下文切換的精確定義可以參考: http://www.linfo.org/context_switch.html. 下面做個簡單的介紹. 多工系統往往需要同時執行多道作業.作業數往往大於機器的CPU數, 然而一顆CPU同時只能執行一項任務, 如何讓使用者感覺這些任務正在同時進行呢? 作業系統的設計者巧妙地利用了時間片輪轉的方式, CPU給每個任務都服務一定的時間, 然後把當前任務的狀態儲存下來, 在載入下一任務的狀態後, 繼續服務下一任務. 任務的狀態儲存及再載入, 這段過程就叫做上下文切換. 時間片輪轉的方式使多個任務在同一顆CPU上執行變成了可能, 但同時也帶來了儲存現場和載入現場的直接消耗.
(Note. 更精確地說, 上下文切換會帶來直接和間接兩種因素影響程式效能的消耗. 直接消耗包括: CPU暫存器需要儲存和載入, 系統排程器的程式碼需要執行, TLB例項需要重新載入, CPU 的pipeline需要刷掉; 間接消耗指的是多核的cache之間得共享資料, 間接消耗對於程式的影響要看執行緒工作區操作資料的大小).
在linux中可以使用vmstat觀察上下文切換的次數. 執行命令如下:
Shell程式碼
- $ vmstat 1
- procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
- r b swpd free buff cache si so bi bo in cs us sy id wa
- 100459394445356011181920014122383061921
- 0004593212453568111881600096958110841942
- 0004593360453568
- 100459340845356811184560000929107341950
- 0004593496453568111845600001133136361930
- 000459356845356811184760000992119041950
vmstat 1指每秒統計一次, 其中cs列就是指上下文切換的數目. 一般情況下, 空閒系統的上下文切換每秒大概在1500以下.
對於我們經常使用的搶佔式作業系統來說, 引起上下文切換的原因大概有以下幾種: 1. 當前執行任務的時間片用完之後, 系統CPU正常排程下一個任務 2. 當前執行任務碰到IO阻塞, 排程器將掛起此任務, 繼續下一任務 3. 多個任務搶佔鎖資源, 當前任務沒有搶到,被排程器掛起, 繼續下一任務 4. 使用者程式碼掛起當前任務, 讓出CPU時間 5. 硬體中斷. 前段時間發現有人在使用futex的WAIT和WAKE來測試context switch的直接消耗(
我做了一個小實驗, 程式碼很簡單, 有兩個工作執行緒. 開始時,第一個執行緒掛起自己; 第二個執行緒喚醒第一個執行緒,再掛起自己; 第一個執行緒醒來之後喚醒第二個執行緒, 再掛起自己. 就這樣一來一往,互相喚醒對方, 掛起自己. 程式碼如下:
Java程式碼
- import java.util.concurrent.atomic.AtomicReference;
- import java.util.concurrent.locks.LockSupport;
- publicfinalclass ContextSwitchTest {
- staticfinalint RUNS = 3;
- staticfinalint ITERATES = 1000000;
- static AtomicReference turn = new AtomicReference();
- staticfinalclass WorkerThread extends Thread {
- volatile Thread other;
- volatileint nparks;
- publicvoid run() {
- final AtomicReference t = turn;
- final Thread other = this.other;
- if (turn == null || other == null)
- thrownew NullPointerException();
- int p = 0;
- for (int i = 0; i < ITERATES; ++i) {
- while (!t.compareAndSet(other, this)) {
- LockSupport.park();
- ++p;
- }
- LockSupport.unpark(other);
- }
- LockSupport.unpark(other);
- nparks = p;
- System.out.println("parks: " + p);
- }
- }
- staticvoid test() throws Exception {
- WorkerThread a = new WorkerThread();
- WorkerThread b = new WorkerThread();
- a.other = b;
- b.other = a;
- turn.set(a);
- long startTime = System.nanoTime();
- a.start();
- b.start();
- a.join();
- b.join();
- long endTime = System.nanoTime();
- int parkNum = a.nparks + b.nparks;
- System.out.println("Average time: " + ((endTime - startTime) / parkNum)
- + "ns");
- }
- publicstaticvoid main(String[] args) throws Exception {
- for (int i = 0; i < RUNS; i++) {
- test();
- }
- }
- }
編譯後,在我自己的筆記本上( Intel(R) Core(TM) i5 CPU M 460 @ 2.53GHz, 2 core, 3M L3 Cache) 用測試幾輪,結果如下:
Shell程式碼
- java -cp . ContextSwitchTest
- parks: 953495
- parks: 953485
- Average time: 11373ns
- parks: 936305
- parks: 936302
- Average time: 11975ns
- parks: 965563
- parks: 965560
- Average time: 13261ns
同時我們可以執行vmstat 1 觀查一下上下文切換的頻率是否變快
Shell程式碼
- $ vmstat 1
- procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
- r b swpd free buff cache si so bi bo in cs us sy id wa
- 100442498845796411549120013122528061921
- 0004420452457964115990000001586206961930
- 1004407676457964117155200001436188383890
- 1004402916457964117203200084229824579294852
- 100441602445796411589120000953821985441710730
- 1104416096457964115896800011679973159934187740
- 100442038445796411547760000962651960761510741
- 100440301245797211710960001521043212135372012662
再使用strace觀察以上程式中Unsafe.park()究竟是哪道系統呼叫造成了上下文切換:
Shell程式碼
- $strace -f java -cp . ContextSwitchTest
- [pid 5969] futex(0x9571a9c, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x9571a98, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
- [pid 5968] <... futex resumed> ) = 0
- [pid 5969] futex(0x9571ad4, FUTEX_WAIT_PRIVATE, 949, NULL <unfinished ...>
- [pid 5968] futex(0x9564368, FUTEX_WAKE_PRIVATE, 1) = 0
- [pid 5968] futex(0x9571ad4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x9571ad0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1} <unfinished ...>
- [pid 5969] <... futex resumed> ) = 0
- [pid 5968] <... futex resumed> ) = 1
- [pid 5969] futex(0x9571628, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
再使用perf看看上下文對於Cache的影響:
Shell程式碼
- $ perf stat -e cache-misses java -cp . ContextSwitchTest
- parks: 999999
- parks: 1000000
- Average time: 16201ns
- parks: 998930
- parks: 998926
- Average time: 14426ns
- parks: 998034
- parks: 998204
- Average time: 14489ns
- Performance counter stats for 'java -cp . ContextSwitchTest':
- 2,550,605 cache-misses
- 90.221827008 seconds time elapsed
嗯, 貌似太長了, 可以結束了. 接下來會繼續幾篇博文繼續分析一些有意思的東西.
(1) 從Java視角看記憶體屏障 (Memory Barrier)
(2) 從java視角看CPU親緣性 (CPU Affinity)
等..敬請關注
PS. 其實還做了一個實驗, 測試CPU Affinity對於Context Switch的影響.
Shell程式碼
- $ taskset -c 0 java -cp . ContextSwitchTest
- parks: 992713
- parks: 1000000
- Average time: 2169ns
- parks: 978428
- parks: 1000000
- Average time: 2196ns
- parks: 989897
- parks: 1000000
- Average time: 2214ns
相關推薦
從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的搶佔式,這裡就不在贅述。 無論是單核還