你不好奇 CPU 是如何執行任務的嗎?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a6/a6fee5ac5c97ecd3aceb7db6bb88c423.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你清楚下面這幾個問題嗎?"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了內存,爲什麼還需要 CPU Cache?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPU 是怎麼讀寫數據的?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何讓 CPU 能讀取數據更快一些?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPU 僞共享是如何發生的?又該如何避免?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPU 是如何調度任務的?如果你的任務對響應要求很高,你希望它總是能被先調度,這該怎麼辦?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"..."}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這篇,我們就來回答這些問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bb/bbc00808f7fb4a7772983153793115f5.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"正文"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"CPU 如何讀寫數據的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先來認識 CPU 的架構,只有理解了 CPU 的 架構,才能更好地理解 CPU 是如何讀寫數據的,對於現代 CPU 的架構圖如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c82e77a1cfb8ce58dc5ccd5887c14b8c.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,一個 CPU 裏通常會有多個 CPU 核心,比如上圖中的 1 號和 2 號 CPU 核心,並且每個 CPU 核心都有自己的 L1 Cache 和 L2 Cache,而 L1 Cache 通常分爲 dCache(數據緩存) 和 iCache(指令緩存),L3 Cache 則是多個核心共享的,這就是 CPU 典型的緩存層次。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面提到的都是 CPU 內部的 Cache,放眼外部的話,還會有內存和硬盤,這些存儲設備共同構成了金字塔存儲層次。如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/31/3195a8229fabb315f894ee6751d10121.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上圖也可以看到,從上往下,存儲設備的容量會越大,而訪問速度會越慢。至於每個存儲設備的訪問延時,你可以看下圖的表格:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/71/7173130778f95493ece4bb181a4f1684.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以看到, CPU 訪問 L1 Cache 速度比訪問內存快 100 倍,這就是爲什麼 CPU 裏會有 L1~L3 Cache 的原因,目的就是把 Cache 作爲 CPU 與內存之間的緩存層,以減少對內存的訪問頻率。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPU 從內存中讀取數據到 Cache 的時候,並不是一個字節一個字節讀取,而是一塊一塊的方式來讀取數據的,這一塊一塊的數據被稱爲 CPU Line(緩存行),所以 "},{"type":"text","marks":[{"type":"strong"}],"text":"CPU Line 是 CPU 從內存讀取數據到 Cache 的單位"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至於 CPU Line 大小,在 Linux 系統可以用下面的方式查看到,你可以看我服務器的 L1 Cache Line 大小是 64 字節,也就意味着 "},{"type":"text","marks":[{"type":"strong"}],"text":"L1 Cache 一次載入數據的大小是 64 字節"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/da/daf796cdc52796bed78d34c738d51ef9.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 那麼對數組的加載, CPU 就會加載數組裏面連續的多個數據到 Cache 裏,因此我們應該按照物理內存地址分佈的順序去訪問元素,這樣訪問數組元素的時候,Cache 命中率就會很高,於是就能減少從內存讀取數據的頻率, 從而可提高程序的性能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 但是,在我們不使用數組,而是使用單獨的變量的時候,則會有 Cache 僞共享的問題,Cache 僞共享問題上是一個性能殺手,我們應該要規避它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來,就來看看 Cache 僞共享是什麼?又如何避免這個問題?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在假設有一個雙核心的 CPU,這兩個 CPU 核心並行運行着兩個不同的線程,它們同時從內存中讀取兩個不同的數據,分別是類型爲 "},{"type":"codeinline","content":[{"type":"text","text":"long"}]},{"type":"text","text":" 的變量 A 和 B,這個兩個數據的地址在物理內存上是"},{"type":"text","marks":[{"type":"strong"}],"text":"連續"},{"type":"text","text":"的,如果 Cahce Line 的大小是 64 字節,並且變量 A 在 Cahce Line 的開頭位置,那麼這兩個數據是位於"},{"type":"text","marks":[{"type":"strong"}],"text":"同一個 Cache Line 中"},{"type":"text","text":",又因爲 CPU Line 是 CPU 從內存讀取數據到 Cache 的單位,所以這兩個數據會被同時讀入到了兩個 CPU 核心中各自 Cache 中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3c/3cb049f3623c4172365d750e6a25cf5c.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來思考一個問題,如果這兩個不同核心的線程分別修改不同的數據,比如 1 號 CPU 核心的線程只修改了 變量 A,或 2 號 CPU 核心的線程的線程只修改了變量 B,會發生什麼呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"分析僞共享的問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在我們結合保證多核緩存一致的 MESI 協議,來說明這一整個的過程,如果你還不知道 MESI 協議,你可以看我這篇文章「"},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/PDUqwAIaUxNkbjvRfovaCg","title":""},"content":[{"type":"text","text":"10 張圖打開 CPU 緩存一致性的大門"}]},{"type":"text","text":"」。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"①. 最開始變量 A 和 B 都還不在 Cache 裏面,假設 1 號核心綁定了線程 A,2 號核心綁定了線程 B,線程 A 只會讀寫變量 A,線程 B 只會讀寫變量 B。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/51/51b2301b0b10b574dea4dc532eee6dc3.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"②. 1 號核心讀取變量 A,由於 CPU 從內存讀取數據到 Cache 的單位是 Cache Line,也正好變量 A 和 變量 B 的數據歸屬於同一個 Cache Line,所以 A 和 B 的數據都會被加載到 Cache,並將此 Cache Line 標記爲「獨佔」狀態。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/57/572fcfaf7c029b1957b7ae9bf07397ad.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"③. 接着,2 號核心開始從內存裏讀取變量 B,同樣的也是讀取 Cache Line 大小的數據到 Cache 中,此 Cache Line 中的數據也包含了變量 A 和 變量 B,此時 1 號和 2 號核心的 Cache Line 狀態變爲「共享」狀態。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c80c098b80caa70142fecf1848672f22.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"④. 1 號核心需要修改變量 A,發現此 Cache Line 的狀態是「共享」狀態,所以先需要通過總線發送消息給 2 號核心,通知 2 號核心把 Cache 中對應的 Cache Line 標記爲「已失效」狀態,然後 1 號核心對應的 Cache Line 狀態變成「已修改」狀態,並且修改變量 A。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/36/369dd0c954e7d87029ac6a5332e7711c.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⑤. 之後,2 號核心需要修改變量 B,此時 2 號核心的 Cache 中對應的 Cache Line 是已失效狀態,另外由於 1 號核心的 Cache 也有此相同的數據,且狀態爲「已修改」狀態,所以要先把 1 號核心的 Cache 對應的 Cache Line 寫回到內存,然後 2 號核心再從內存讀取 Cache Line 大小的數據到 Cache 中,最後把變量 B 修改到 2 號核心的 Cache 中,並將狀態標記爲「已修改」狀態。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7a/7a27b4aa80c086e69ffe2afa83601c52.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,可以發現如果 1 號和 2 號 CPU 核心這樣持續交替的分別修改變量 A 和 B,就會重複 ④ 和 ⑤ 這兩個步驟,Cache 並沒有起到緩存的效果,雖然變量 A 和 B 之間其實並沒有任何的關係,但是因爲同時歸屬於一個 Cache Line ,這個 Cache Line 中的任意數據被修改後,都會相互影響,從而出現 ④ 和 ⑤ 這兩個步驟。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,這種因爲多個線程同時讀寫同一個 Cache Line 的不同變量時,而導致 CPU Cache 失效的現象稱爲*"},{"type":"text","marks":[{"type":"italic"}],"text":"僞共享("},{"type":"text","text":"False Sharing"},{"type":"text","marks":[{"type":"italic"}],"text":")"},{"type":"text","text":"*。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"避免僞共享的方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,對於多個線程共享的熱點數據,即經常會修改的數據,應該避免這些數據剛好在同一個 Cache Line 中,否則就會出現爲僞共享的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來,看看在實際項目中是用什麼方式來避免僞共享的問題的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Linux 內核中存在 "},{"type":"codeinline","content":[{"type":"text","text":"__cacheline_aligned_in_smp"}]},{"type":"text","text":" 宏定義,是用於解決僞共享的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/18/18d82a849ded6f81e4dee103a4fa8d94.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面的宏定義,我們可以看到:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果在多核(MP)系統裏,該宏定義是 "},{"type":"codeinline","content":[{"type":"text","text":"__cacheline_aligned"}]},{"type":"text","text":",也就是 Cache Line 的大小;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而如果在單核系統裏,該宏定義是空的;"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,針對在同一個 Cache Line 中的共享的數據,如果在多核之間競爭比較嚴重,爲了防止僞共享現象的發生,可以採用上面的宏定義使得變量在 Cache Line 裏是對齊的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉個例子,有下面這個結構體:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cd/cde18c30d1719c85359be4884fd93a7b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"結構體裏的兩個成員變量 a 和 b 在物理內存地址上是連續的,於是它們可能會位於同一個 Cache Line 中,如下圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fc/fcf28152b7fa66399f2b0d97c63c2f53.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,爲了防止前面提到的 Cache 僞共享問題,我們可以使用上面介紹的宏定義,將 b 的地址設置爲 Cache Line 對齊地址,如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c6/c6fecac537be5e50574b69c3294d331d.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣 a 和 b 變量就不會在同一個 Cache Line 中了,如下圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7e/7e1731340bb1e41f8c12bf5a20e2f3b4.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,避免 Cache 僞共享實際上是用空間換時間的思想,浪費一部分 Cache 空間,從而換來性能的提升。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們再來看一個應用層面的規避方案,有一個 Java 併發框架 Disruptor 使用「字節填充 + 繼承」的方式,來避免僞共享的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Disruptor 中有一個 RingBuffer 類會經常被多個線程使用,代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ac/acef1063a14deea9f049f19c8b4e950b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可能會覺得 RingBufferPad 類裏 7 個 long 類型的名字很奇怪,但事實上,它們雖然看起來毫無作用,但卻對性能的提升起到了至關重要的作用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們都知道,CPU Cache 從內存讀取數據的單位是 CPU Line,一般 64 位 CPU 的 CPU Line 的大小是 64 個字節,一個 long 類型的數據是 8 個字節,所以 CPU 一下會加載 8 個 long 類型的數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據 JVM 對象繼承關係中父類成員和子類成員,內存地址是連續排列布局的,因此 RingBufferPad 中的 7 個 long 類型數據作爲 Cache Line "},{"type":"text","marks":[{"type":"strong"}],"text":"前置填充"},{"type":"text","text":",而 RingBuffer 中的 7 個 long 類型數據則作爲 Cache Line "},{"type":"text","marks":[{"type":"strong"}],"text":"後置填充"},{"type":"text","text":",這 14 個 long 變量沒有任何實際用途,更不會對它們進行讀寫操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a0/a00e961a867d40662ea2248a018bc0ff.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,RingBufferFelds 裏面定義的這些變量都是 "},{"type":"codeinline","content":[{"type":"text","text":"final"}]},{"type":"text","text":" 修飾的,意味着第一次加載之後不會再修改, 又"},{"type":"text","marks":[{"type":"strong"}],"text":"由於「前後」各填充了 7 個不會被讀寫的 long 類型變量,所以無論怎麼加載 Cache Line,這整個 Cache Line 裏都沒有會發生更新操作的數據,於是只要數據被頻繁地讀取訪問,就自然沒有數據被換出 Cache 的可能,也因此不會產生僞共享的問題"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"CPU 如何選擇線程的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瞭解完 CPU 讀取數據的過程後,我們再來看看 CPU 是根據什麼來選擇當前要執行的線程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Linux 內核中,進程和線程都是用 "},{"type":"codeinline","content":[{"type":"text","text":"tark_struct"}]},{"type":"text","text":" 結構體表示的,區別在於線程的 tark"},{"type":"text","marks":[{"type":"italic"}],"text":"struct 結構體裏部分資源是共享了進程已創建的資源,比如內存地址空間、代碼段、文件描述符等,所以 Linux 中的線程也被稱爲輕量級進程,因爲線程的 tark"},{"type":"text","text":"struct 相比進程的 tark_struct 承載的 資源比較少,因此以「輕」得名。 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般來說,沒有創建線程的進程,是隻有單個執行流,它被稱爲是主線程。如果想讓進程處理更多的事情,可以創建多個線程分別去處理,但不管怎麼樣,它們對應到內核裏都是 "},{"type":"codeinline","content":[{"type":"text","text":"tark_struct"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/40/40babd581cbb64638c323e1a6fbdd4d9.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,Linux 內核裏的調度器,調度的對象就是 "},{"type":"codeinline","content":[{"type":"text","text":"tark_struct"}]},{"type":"text","text":",接下來我們就把這個數據結構統稱爲"},{"type":"text","marks":[{"type":"strong"}],"text":"任務"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Linux 系統中,根據任務的優先級以及響應要求,主要分爲兩種,其中優先級的數值越小,優先級越高:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實時任務,對系統的響應時間要求很高,也就是要儘可能快的執行實時任務,優先級在 "},{"type":"codeinline","content":[{"type":"text","text":"0~99"}]},{"type":"text","text":" 範圍內的就算實時任務;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"普通任務,響應時間沒有很高的要求,優先級在 "},{"type":"codeinline","content":[{"type":"text","text":"100~139"}]},{"type":"text","text":" 範圍內都是普通任務級別;"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"調度類"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於任務有優先級之分,Linux 系統爲了保障高優先級的任務能夠儘可能早的被執行,於是分爲了這幾種調度類,如下圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d6/d62c16c921cd7bbe943a00eb2f34de77.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Deadline 和 Realtime 這兩個調度類,都是應用於實時任務的,這兩個調度類的調度策略合起來共有這三種,它們的作用如下: "}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"SCHED_DEADLINE"},{"type":"text","text":":是按照 deadline 進行調度的,距離當前時間點最近的 deadline 的任務會被優先調度;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"SCHED_FIFO"},{"type":"text","text":":對於相同優先級的任務,按先來先服務的原則,但是優先級更高的任務,可以搶佔低優先級的任務,也就是優先級高的可以「插隊」;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"SCHED_RR"},{"type":"text","text":":對於相同優先級的任務,輪流着運行,每個任務都有一定的時間片,當用完時間片的任務會被放到隊列尾部,以保證相同優先級任務的公平性,但是高優先級的任務依然可以搶佔低優先級的任務;"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 Fair 調度類是應用於普通任務,都是由 CFS 調度器管理的,分爲兩種調度策略:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"SCHED_NORMAL"},{"type":"text","text":":普通任務使用的調度策略;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"SCHED_BATCH"},{"type":"text","text":":後臺任務的調度策略,不和終端進行交互,因此在不影響其他需要交互的任務,可以適當降低它的優先級。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"完全公平調度"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們平日裏遇到的基本都是普通任務,對於普通任務來說,公平性最重要,在 Linux 裏面,實現了一個基於 CFS 的調度算法,也就是*"},{"type":"text","marks":[{"type":"italic"}],"text":"完全公平調度("},{"type":"text","text":"Completely Fair Scheduling"},{"type":"text","marks":[{"type":"italic"}],"text":")"},{"type":"text","text":"*。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個算法的理念是想讓分配給每個任務的 CPU 時間是一樣,於是它爲每個任務安排一個虛擬運行時間 vruntime,如果一個任務在運行,其運行的越久,該任務的 vruntime 自然就會越大,而沒有被運行的任務,vruntime 是不會變化的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼,"},{"type":"text","marks":[{"type":"strong"}],"text":"在 CFS 算法調度的時候,會優先選擇 vruntime 少的任務"},{"type":"text","text":",以保證每個任務的公平性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就好比,讓你把一桶的奶茶平均分到 10 杯奶茶杯裏,你看着哪杯奶茶少,就多倒一些;哪個多了,就先不倒,這樣經過多輪操作,雖然不能保證每杯奶茶完全一樣多,但至少是公平的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,上面提到的例子沒有考慮到優先級的問題,雖然是普通任務,但是普通任務之間還是有優先級區分的,所以在計算虛擬運行時間 vruntime 還要考慮普通任務的"},{"type":"text","marks":[{"type":"strong"}],"text":"權重值"},{"type":"text","text":",注意權重值並不是優先級的值,內核中會有一個 nice 級別與權重值的轉換表,nice 級別越低的權重值就越大,至於 nice 值是什麼,我們後面會提到。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"於是就有了以下這個公式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c8d9cb54b02030ca3add0121bfebe7bd.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以不用管 NICE"},{"type":"text","marks":[{"type":"italic"}],"text":"0"},{"type":"text","text":"LOAD 是什麼,你就認爲它是一個常量,那麼在「同樣的實際運行時間」裏,高權重任務的 vruntime 比低權重任務的 vruntime "},{"type":"text","marks":[{"type":"strong"}],"text":"少"},{"type":"text","text":",你可能會奇怪爲什麼是少的?你還記得 CFS 調度嗎,它是會優先選擇 vruntime 少的任務進行調度,所以高權重的任務就會被優先調度了,於是高權重的獲得的實際運行時間自然就多了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"CPU 運行隊列"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個系統通常都會運行着很多任務,多任務的數量基本都是遠超 CPU 核心數量,因此這時候就需要"},{"type":"text","marks":[{"type":"strong"}],"text":"排隊"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事實上,每個 CPU 都有自己的*"},{"type":"text","marks":[{"type":"italic"}],"text":"運行隊列("},{"type":"text","text":"Run Queue, rq"},{"type":"text","marks":[{"type":"italic"}],"text":")"},{"type":"text","text":"*,用於描述在此 CPU 上所運行的所有進程,其隊列包含三個運行隊列,Deadline 運行隊列 dl"},{"type":"text","marks":[{"type":"italic"}],"text":"rq、實時任務運行隊列 rt"},{"type":"text","text":"rq 和 CFS 運行隊列 csf"},{"type":"text","marks":[{"type":"italic"}],"text":"rq,其中 csf"},{"type":"text","text":"rq 是用紅黑樹來描述的,按 vruntime 大小來排序的,最左側的葉子節點,就是下次會被調度的任務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d8/d8470503a7e83a9dee3544dc0a0dc13b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這幾種調度類是有優先級的,優先級如下:Deadline > Realtime > Fair,這意味着 Linux 選擇下一個任務執行的時候,會按照此優先級順序進行選擇,也就是說先從 "},{"type":"codeinline","content":[{"type":"text","text":"dl_rq"}]},{"type":"text","text":" 裏選擇任務,然後從 "},{"type":"codeinline","content":[{"type":"text","text":"rt_rq"}]},{"type":"text","text":" 裏選擇任務,最後從 "},{"type":"codeinline","content":[{"type":"text","text":"csf_rq"}]},{"type":"text","text":" 裏選擇任務。因此,"},{"type":"text","marks":[{"type":"strong"}],"text":"實時任務總是會比普通任務優先被執行"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"調整優先級"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們啓動任務的時候,沒有特意去指定優先級的話,默認情況下都是普通任務,普通任務的調度類是 Fail,由 CFS 調度器來進行管理。CFS 調度器的目的是實現任務運行的公平性,也就是保障每個任務的運行的時間是差不多的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你想讓某個普通任務有更多的執行時間,可以調整任務的 "},{"type":"codeinline","content":[{"type":"text","text":"nice"}]},{"type":"text","text":" 值,從而讓優先級高一些的任務執行更多時間。nice 的值能設置的範圍是 "},{"type":"codeinline","content":[{"type":"text","text":"-20~19"}]},{"type":"text","text":", 值越低,表明優先級越高,因此 -20 是最高優先級,19 則是最低優先級,默認優先級是 0。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是不是覺得 nice 值的範圍很詭異?事實上,nice 值並不是表示優先級,而是表示優先級的修正數值,它與優先級(priority)的關係是這樣的:priority(new) = priority(old) + nice。內核中,priority 的範圍是 0~139,值越低,優先級越高,其中前面的 0~99 範圍是提供給實時任務使用的,而 nice 值是映射到 100~139,這個範圍是提供給普通任務用的,因此 nice 值調整的是普通任務的優先級。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/75/7599c0f8ede8d3eb9f67cbf1fb177031.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在前面我們提到了,權重值與 nice 值的關係的,nice 值越低,權重值就越大,計算出來的 vruntime 就會越少,由於 CFS 算法調度的時候,就會優先選擇 vruntime 少的任務進行執行,所以 nice 值越低,任務的優先級就越高。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以在啓動任務的時候,可以指定 nice 的值,比如將 mysqld 以 -3 優先級:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b3/b33846f46dcceb726f2365132ec7ce0a.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果想修改已經運行中的任務的優先級,則可以使用 "},{"type":"codeinline","content":[{"type":"text","text":"renice"}]},{"type":"text","text":" 來調整 nice 值:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d9/d950751e3443215ff8037d436bce9d01.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"nice 調整的是普通任務的優先級,所以不管怎麼縮小 nice 值,任務永遠都是普通任務,如果某些任務要求實時性比較高,那麼你可以考慮改變任務的優先級以及調度策略,使得它變成實時任務,比如:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ef/effbe4e0845544ddfba68106229b47af.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"理解 CPU 是如何讀寫數據的前提,是要理解 CPU 的架構,CPU 內部的多個 Cache + 外部的內存和磁盤都就構成了金字塔的存儲器結構,在這個金字塔中,越往下,存儲器的容量就越大,但訪問速度就會小。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPU 讀寫數據的時候,並不是按一個一個字節爲單位來進行讀寫,而是以 CPU Line 大小爲單位,CPU Line 大小一般是 64 個字節,也就意味着 CPU 讀寫數據的時候,每一次都是以 64 字節大小爲一塊進行操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,如果我們操作的數據是數組,那麼訪問數組元素的時候,按內存分佈的地址順序進行訪問,這樣能充分利用到 Cache,程序的性能得到提升。但如果操作的數據不是數組,而是普通的變量,並在多核 CPU 的情況下,我們還需要避免 Cache Line 僞共享的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所謂的 Cache Line 僞共享問題就是,多個線程同時讀寫同一個 Cache Line 的不同變量時,而導致 CPU Cache 失效的現象。那麼對於多個線程共享的熱點數據,即經常會修改的數據,應該避免這些數據剛好在同一個 Cache Line 中,避免的方式一般有 Cache Line 大小字節對齊,以及字節填充等方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"系統中需要運行的多線程數一般都會大於 CPU 核心,這樣就會導致線程排隊等待 CPU,這可能會產生一定的延時,如果我們的任務對延時容忍度很低,則可以通過一些人爲手段干預 Linux 的默認調度策略和優先級。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":" CPU 系列好文"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/TxFzIgNLettiEO4JKWgQpQ","title":""},"content":[{"type":"text","text":"CPU 執行程序的祕密,藏在了這 15 張圖裏"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/-E5jcp7tfkXjsSu2vzdeAw","title":""},"content":[{"type":"text","text":"知道硬盤很慢,但沒想到比 CPU Cache 慢 10000000 倍"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/-uhAhBD2zGl_h19E4fNJzQ","title":""},"content":[{"type":"text","text":"如何寫出讓 CPU 跑得更快的代碼?"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/PDUqwAIaUxNkbjvRfovaCg","title":""},"content":[{"type":"text","text":"10 張圖打開 CPU 緩存一致性的大門"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d8/d818ad36888ec8474c6a860ea96efc59.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"哈嘍,我是小林,就愛圖解計算機基礎,如果覺得文章對你有幫助,歡迎分享給你的朋友,也給小林點個「贊」,這對小林非常重要,謝謝你們,給各位小姐姐小哥哥們抱拳了,我們下次見!"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章