成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

字節面:什么是偽共享?

開發 前端
周末的時候,有個讀者跟我說,面試字節的時候被問到:「什么是偽共享?又該怎么避免偽共享的問題?」這個其實是考察 CPU 緩存的問題,我之前的圖解系統也有提到過。

 

本文轉載自微信公眾號「小林coding」,作者小林coding 。轉載本文請聯系小林coding公眾號。

大家好,我是小林。

周末的時候,有個讀者跟我說,面試字節的時候被問到:「什么是偽共享?又該怎么避免偽共享的問題?」

這個其實是考察 CPU 緩存的問題,我之前的圖解系統也有提到過。

今天,我再跟大家講一下。

正文

CPU 如何讀寫數據的?

先來認識 CPU 的架構,只有理解了 CPU 的 架構,才能更好地理解 CPU 是如何讀寫數據的,對于現代 CPU 的架構圖如下:

可以看到,一個 CPU 里通常會有多個 CPU 核心,比如上圖中的 1 號和 2 號 CPU 核心,并且每個 CPU 核心都有自己的 L1 Cache 和 L2 Cache,而 L1 Cache 通常分為 dCache(數據緩存) 和 iCache(指令緩存),L3 Cache 則是多個核心共享的,這就是 CPU 典型的緩存層次。

上面提到的都是 CPU 內部的 Cache,放眼外部的話,還會有內存和硬盤,這些存儲設備共同構成了金字塔存儲層次。如下圖所示:

從上圖也可以看到,從上往下,存儲設備的容量會越大,而訪問速度會越慢。至于每個存儲設備的訪問延時,你可以看下圖的表格:

你可以看到, CPU 訪問 L1 Cache 速度比訪問內存快 100 倍,這就是為什么 CPU 里會有 L1~L3 Cache 的原因,目的就是把 Cache 作為 CPU 與內存之間的緩存層,以減少對內存的訪問頻率。

CPU 從內存中讀取數據到 Cache 的時候,并不是一個字節一個字節讀取,而是一塊一塊的方式來讀取數據的,這一塊一塊的數據被稱為 CPU Line(緩存行),所以 CPU Line 是 CPU 從內存讀取數據到 Cache 的單位。

至于 CPU Line 大小,在 Linux 系統可以用下面的方式查看到,你可以看我服務器的 L1 Cache Line 大小是 64 字節,也就意味著 L1 Cache 一次載入數據的大小是 64 字節。

那么對數組的加載, CPU 就會加載數組里面連續的多個數據到 Cache 里,因此我們應該按照物理內存地址分布的順序去訪問元素,這樣訪問數組元素的時候,Cache 命中率就會很高,于是就能減少從內存讀取數據的頻率, 從而可提高程序的性能。

但是,在我們不使用數組,而是使用單獨的變量的時候,則會有 Cache 偽共享的問題,Cache 偽共享問題上是一個性能殺手,我們應該要規避它。

接下來,就來看看 Cache 偽共享是什么?又如何避免這個問題?

現在假設有一個雙核心的 CPU,這兩個 CPU 核心并行運行著兩個不同的線程,它們同時從內存中讀取兩個不同的數據,分別是類型為 long 的變量 A 和 B,這個兩個數據的地址在物理內存上是連續的,如果 Cahce Line 的大小是 64 字節,并且變量 A 在 Cahce Line 的開頭位置,那么這兩個數據是位于同一個 Cache Line 中,又因為 CPU Line 是 CPU 從內存讀取數據到 Cache 的單位,所以這兩個數據會被同時讀入到了兩個 CPU 核心中各自 Cache 中。

我們來思考一個問題,如果這兩個不同核心的線程分別修改不同的數據,比如 1 號 CPU 核心的線程只修改了 變量 A,或 2 號 CPU 核心的線程的線程只修改了變量 B,會發生什么呢?

分析偽共享的問題

現在我們結合保證多核緩存一致的 MESI 協議,來說明這一整個的過程,如果你還不知道 MESI 協議,你可以看我這篇文章「10 張圖打開 CPU 緩存一致性的大門」。

①. 最開始變量 A 和 B 都還不在 Cache 里面,假設 1 號核心綁定了線程 A,2 號核心綁定了線程 B,線程 A 只會讀寫變量 A,線程 B 只會讀寫變量 B。

②. 1 號核心讀取變量 A,由于 CPU 從內存讀取數據到 Cache 的單位是 Cache Line,也正好變量 A 和 變量 B 的數據歸屬于同一個 Cache Line,所以 A 和 B 的數據都會被加載到 Cache,并將此 Cache Line 標記為「獨占」狀態。

③. 接著,2 號核心開始從內存里讀取變量 B,同樣的也是讀取 Cache Line 大小的數據到 Cache 中,此 Cache Line 中的數據也包含了變量 A 和 變量 B,此時 1 號和 2 號核心的 Cache Line 狀態變為「共享」狀態。

④. 1 號核心需要修改變量 A,發現此 Cache Line 的狀態是「共享」狀態,所以先需要通過總線發送消息給 2 號核心,通知 2 號核心把 Cache 中對應的 Cache Line 標記為「已失效」狀態,然后 1 號核心對應的 Cache Line 狀態變成「已修改」狀態,并且修改變量 A。

⑤. 之后,2 號核心需要修改變量 B,此時 2 號核心的 Cache 中對應的 Cache Line 是已失效狀態,另外由于 1 號核心的 Cache 也有此相同的數據,且狀態為「已修改」狀態,所以要先把 1 號核心的 Cache 對應的 Cache Line 寫回到內存,然后 2 號核心再從內存讀取 Cache Line 大小的數據到 Cache 中,最后把變量 B 修改到 2 號核心的 Cache 中,并將狀態標記為「已修改」狀態。

所以,可以發現如果 1 號和 2 號 CPU 核心這樣持續交替的分別修改變量 A 和 B,就會重復 ④ 和 ⑤ 這兩個步驟,Cache 并沒有起到緩存的效果,雖然變量 A 和 B 之間其實并沒有任何的關系,但是因為同時歸屬于一個 Cache Line ,這個 Cache Line 中的任意數據被修改后,都會相互影響,從而出現 ④ 和 ⑤ 這兩個步驟。

因此,這種因為多個線程同時讀寫同一個 Cache Line 的不同變量時,而導致 CPU Cache 失效的現象稱為偽共享(False Sharing)。

避免偽共享的方法

因此,對于多個線程共享的熱點數據,即經常會修改的數據,應該避免這些數據剛好在同一個 Cache Line 中,否則就會出現為偽共享的問題。

接下來,看看在實際項目中是用什么方式來避免偽共享的問題的。

在 Linux 內核中存在 __cacheline_aligned_in_smp 宏定義,是用于解決偽共享的問題。

從上面的宏定義,我們可以看到:

  • 如果在多核(MP)系統里,該宏定義是 __cacheline_aligned,也就是 Cache Line 的大小;
  • 而如果在單核系統里,該宏定義是空的;

因此,針對在同一個 Cache Line 中的共享的數據,如果在多核之間競爭比較嚴重,為了防止偽共享現象的發生,可以采用上面的宏定義使得變量在 Cache Line 里是對齊的。

舉個例子,有下面這個結構體:

結構體里的兩個成員變量 a 和 b 在物理內存地址上是連續的,于是它們可能會位于同一個 Cache Line 中,如下圖:

所以,為了防止前面提到的 Cache 偽共享問題,我們可以使用上面介紹的宏定義,將 b 的地址設置為 Cache Line 對齊地址,如下:

這樣 a 和 b 變量就不會在同一個 Cache Line 中了,如下圖:

所以,避免 Cache 偽共享實際上是用空間換時間的思想,浪費一部分 Cache 空間,從而換來性能的提升。

我們再來看一個應用層面的規避方案,有一個 Java 并發框架 Disruptor 使用「字節填充 + 繼承」的方式,來避免偽共享的問題。

Disruptor 中有一個 RingBuffer 類會經常被多個線程使用,代碼如下:

你可能會覺得 RingBufferPad 類里 7 個 long 類型的名字很奇怪,但事實上,它們雖然看起來毫無作用,但卻對性能的提升起到了至關重要的作用。

我們都知道,CPU Cache 從內存讀取數據的單位是 CPU Line,一般 64 位 CPU 的 CPU Line 的大小是 64 個字節,一個 long 類型的數據是 8 個字節,所以 CPU 一下會加載 8 個 long 類型的數據。

根據 JVM 對象繼承關系中父類成員和子類成員,內存地址是連續排列布局的,因此 RingBufferPad 中的 7 個 long 類型數據作為 Cache Line 前置填充,而 RingBuffer 中的 7 個 long 類型數據則作為 Cache Line 后置填充,這 14 個 long 變量沒有任何實際用途,更不會對它們進行讀寫操作。

另外,RingBufferFelds 里面定義的這些變量都是 final 修飾的,意味著第一次加載之后不會再修改, 又由于「前后」各填充了 7 個不會被讀寫的 long 類型變量,所以無論怎么加載 Cache Line,這整個 Cache Line 里都沒有會發生更新操作的數據,于是只要數據被頻繁地讀取訪問,就自然沒有數據被換出 Cache 的可能,也因此不會產生偽共享的問題。

 

責任編輯:武曉燕 來源: 小林coding
相關推薦

2021-03-01 11:53:15

面試偽共享CPU

2021-06-30 17:38:03

Trie 樹字符Java

2024-07-30 14:01:51

Java字節碼JVM?

2025-03-28 10:47:05

開發注解Java

2024-08-30 08:59:15

2025-04-08 09:20:00

Sentinel限流微服務

2022-12-12 08:39:09

CPUCache偽共享

2017-07-13 16:40:16

偽共享緩存行存儲

2021-04-25 09:58:48

mmapJava面試

2019-12-17 14:24:11

CPU緩存偽共享

2021-03-17 15:54:32

IO零拷貝方式

2024-11-26 08:52:34

SQL優化Kafka

2013-06-14 10:12:22

共享并行

2021-11-18 08:55:49

共享CPU內存

2022-03-30 10:10:17

字節碼棧空間

2015-04-23 09:34:51

Windows開源

2023-12-26 16:14:43

2017-08-23 13:21:31

2021-09-08 07:58:58

字節系統雙寫

2024-04-03 09:01:34

SpringTomcat容器
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天天视频一区二区三区 | 久久国产综合 | 一区二区精品 | 亚洲综合无码一区二区 | 天天艹日日干 | 免费能直接在线观看黄的视频 | 国产成人免费在线 | 日本二区 | 午夜小视频在线观看 | 久久中文一区二区 | 国产偷自视频区视频 | 玖玖视频网 | 日韩精品专区在线影院重磅 | 国产视频一区二区 | 国产中文字幕在线观看 | 男女羞羞的网站 | 中文字幕一区二区三区四区不卡 | 2021天天干夜夜爽 | 久久ww| 国产欧美日韩一区二区三区在线观看 | 国产一区二区三区四区 | 亚洲久草视频 | 亚洲性视频网站 | 日韩一级 | 国产日韩欧美一区 | 国产精品美女久久久久久久网站 | 欧美激情一区二区 | 色眯眯视频在线观看 | 欧美精品在线观看 | 欧美成人a∨高清免费观看 91伊人 | 国内精品视频一区二区三区 | 天天插天天操 | 亚州视频在线 | 欧洲一区二区在线 | 久久久www成人免费无遮挡大片 | 日韩精品1区2区3区 爱爱综合网 | 成av在线 | 成人在线精品视频 | 久久在线| 欧美日韩高清 | 欧美一区二区在线免费观看 |