偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

并發(fā)原理之MESI與內存屏障

存儲 存儲軟件
現代的CPU比內存系統(tǒng)快很多,2006年的cpu可以在一納秒之內執(zhí)行10條指令,但是需要多個十納秒去從內存讀取一個數據,這里面產生了至少兩個數量級的速度差距。

 現代的CPU比內存系統(tǒng)快很多,2006年的cpu可以在一納秒之內執(zhí)行10條指令,但是需要多個十納秒去從內存讀取一個數據,這里面產生了至少兩個數量級的速度差距。在這樣的問題下,cpu cache應運而生。

[[323140]]

cache處于cpu與內存之間,讀寫速度比內存快很多,但也比較昂貴。

并發(fā)原理系列一:MESI與內存屏障

cache是以cache line為基本單位來進行讀寫的,cache line的大小是2的冪次,從16字節(jié)到256字節(jié)不等。

并發(fā)原理系列一:MESI與內存屏障

上圖就是一個cpu cache的架構示意圖,總共有32個cache line,每個cache line是256字節(jié)。cacheline的起始地址低8位都是0,使用內存地址的9-12位數據來進行hash。

當cpu在cache里尋找數據時,如果數據不存在,則會產生一個cache miss,這時候cpu需要等待數據從內存讀回,這需要耗費很長時間,但是因為讀取之后會存儲到cache中,所以以后的讀取就會變得非???。為了減少cache miss造成的性能損失,現代的cpu單核可以超線程,一個線程等待的時候,另一個線程就能執(zhí)行指令了。

為什么會有cache miss,一種是數據預熱,機器剛啟動的時候,cache是沒有數據的,還有一種情況是cache不足,需要淘汰舊的數據。

我們用的最多的緩存一致性協(xié)議是MESI,四個字母分別表示modified, exclusive, shared, invalid,這是cache line的四種狀態(tài),

modified:數據是被該cpu獨占的,其他cpu沒有存儲該數據。

exclusive:這個狀態(tài)跟modified很類似,只是該狀態(tài)下,cache的數據已經同步到主存了,所以即使丟棄也無所謂。

shared:數據存在于多個cpu cache里,每個cpu對該數據只能讀,而不能簡單地寫

invalid:該cache line是空的

Read:read消息會帶上cache line的物理內存地址向其他cpu獲取數據

Read Response:如果其他cpu有這個cache line,并且處于modified,那么該cpu必須返回該消息,因為其他cpu的cache line和主存都沒有最新的數據

Invalidate:invalidate消息會帶上cache line的物理內存地址,來讓其他cache把相應的數據從cache line里去除掉

Invalidate Acknowledge:如果一個cpu收到Invalidate消息,那么它必須在刪除數據之后返回該消息

Read Invalidate:該消息是Read和Invalidate的組合,所以它需要一個Read Response和多個Invalidate Acknowledge

Writeback:modified狀態(tài)的cache line寫到主存,可以用來騰出空間給其他數據

我們現在來看一下MESI各種狀態(tài)之間的遷移:

并發(fā)原理系列一:MESI與內存屏障

a) cpu把cacheline 回寫到內存,此時該cpu對這個cacheline還是有獨占權

b) cacheline 被cpu修改,該操作不需要cpu之間通信

c) cpu收到read invalidate之后,本地cacheline失效

d) cacheline 被本地cpu修改,需要和其他cpu通信,發(fā)出read invalidate 獲取最新的數據

e) cacheline 被本地cpu修改,需要向其他cpu發(fā)出invalidate請求

f) 其他cpu發(fā)來read請求

g) 其他cpu發(fā)來read請求

h) cpu意識到它馬上要寫數據到cacheline,所以提前發(fā)出invalidate消息給其他cpu

i) 其他cpu發(fā)來read invalidate

j) cpu在寫數據之前發(fā)出read invalidate消息給其他cpu,之后就處于e狀態(tài),該狀態(tài)很快就可能變成m狀態(tài)

k) cpu發(fā)出read請求

l) 收到invalidate請求

雖然MESI協(xié)議能保證讀寫內存的高性能,但還是有點問題:

并發(fā)原理系列一:MESI與內存屏障

當cpu0要寫數據到本地cache的時候,如果不是M或者E狀態(tài),需要發(fā)送一個invalidate消息給cpu1,只有收到cpu1的acknowledgement才能寫數據到cache中,在這個過程中cpu0需要等待,這大大影響了性能。一種解決辦法是在cpu和cache之間引入store buffer,當發(fā)出invalidate之后直接把數據寫入store buffer。當收到acknowledgement之后可以把store buffer中的數據寫入cache?,F在的架構圖是這樣的:

并發(fā)原理系列一:MESI與內存屏障

現在這樣的架構引入了復雜性,看下面的例子:

cpu0cache里面有個b,初值為0,cpu1cache有個a,初值為0,現在cpu0運行代碼

  1. a=1; 
  2. b=a+1; 
  3. assert(b==2) 

cpu0執(zhí)行a=1的時候發(fā)現本地cache沒有a,所以發(fā)送read invalidate給cpu1,然后把a=1寫入store buffer

cpu1收到read invalidate之后把a傳給cpu0并且本地cacheline置為無效

cpu0開始執(zhí)行b=a+1

cpu0收到cpu1的read response,發(fā)現a=0

cpu0執(zhí)行a+1,得到1賦給b

cpu0執(zhí)行最后一句,失敗

這里關鍵的問題是cpu會把自己的操作看做是全局的內存操作,但其實操作storebuffer沒有操作到主存,所以我們需要在查cache的時候還得查一下store buffer,這種技術叫做store forwarding.

現在的架構是這樣的:

 并發(fā)原理系列一:MESI與內存屏障

上面是store buffer在一個cpu中碰到的問題,在多個cpu并發(fā)的過程中也可能存在問題,看下例:

  1. void foo(void) 
  2. a = 1; 
  3. b = 1; 
  4. void bar(void) 
  5. while (b == 0) continue
  6. assert(a == 1); 

同樣的,cpu0cache里面有個b,初值為0,cpu1cache有個a,初值為0,現在cpu0運行foo, cpu1運行bar

cpu0 發(fā)現a不在本地cache,發(fā)送read invalidate去cpu1,并在store buffer中把a置為1

cpu1 執(zhí)行while (b == 0)發(fā)現b不在本地內存,發(fā)送read消息去cpu0

cpu0 在本地cache置b為1

cpu0收到read消息,把cache中的b傳送給cpu1,并把本地狀態(tài)置為s

cpu1發(fā)現b為1,退出循環(huán),因為這時候cpu1本地cache中a還是1,所以失敗

cpu1收到read invalidate,把a傳輸給cpu0,并置本地cache為invalidate但是太晚了

cpu0收到cpu1關于a的read response,把本地的store buffer移到cache中

第一個問題硬件工程署可以解決,但是第二個很難處理,因為硬件無法知道變量之間的依賴關系,硬件工程師設計了memory barrier(內存屏障),軟件可以使用這個工具來提示cpu變量之間的關系。新的代碼如下:

  1. void foo(void) 
  2. a = 1; 
  3. smp_mb(); 
  4. b = 1; 
  5. void bar(void) 
  6. while (b == 0) continue
  7. assert(a == 1); 

內存屏障smp_mb()提示cpu在進行smp_mb之后的存儲的時候,會先把store buffer里的數據刷新到cache中。有兩種方式,1:cpu會等到store buffer清空之后再處理其他指令,或者2:之后的所有寫操作都不寫到cache,而是寫到store buffer中,直到smp_mb之前的store buffer中的數據刷新到cache中。

上例中的執(zhí)行效果如下:

cpu0執(zhí)行 a=1,發(fā)現a不在本地cache中,進而把a=1寫入store buffer,并發(fā)出read invalidate消息給cpu1

cpu1執(zhí)行while (b == 0),發(fā)現b不在本地cache中,進而發(fā)出read消息給cpu0

cpu0執(zhí)行smp_mb,把store buffer中的a標記一下

cpu0執(zhí)行b=1 發(fā)現狀態(tài)為獨占,所以可以直接寫,但是因為store buffer中有標記過的值,所以把b=1寫入store buffer,但是不標記

cpu0收到read消息,把cache中b的數據0發(fā)給cpu1,并把cacheline置為s

cpu1收到b=0,陷入循環(huán)中

cpu0收到read invalidate消息,進而把a=1從store buffer寫入cache,這時候可以把store buffer中的b=1寫入cache,但是發(fā)現這時候cache中的b屬于s狀態(tài),所以發(fā)出invalidate消息給cpu1

cpu1收到invalidate消息之后把b設為1

cpu0收到invalidate ack之后把b的值1寫入cache

cpu1要讀取b的值,發(fā)出read消息給cpu0,

cpu0把b=1發(fā)給cpu1

cpu1收到b的值1,退出循環(huán)

cpu1發(fā)現a無效,發(fā)出read消息給cpu0

cpu0把a的值1發(fā)送給cpu1,并且把a置為s

cpu1得到a=1,成功

但是內存屏障的處理方法有個問題,那就是store buffer空間是有限的,如果store buffer中的空間被smp_mb之后的存儲塞滿,cpu還是得等待invalidate消息返回才能繼續(xù)處理。解決這種問題的思路是讓invalidate ack能更早得返回,一種辦法是提供一種放置invalidate message的隊列,稱為invalidate queue. cpu可以在收到invalidate之后馬上返回invalidate ack,而不是在把本地cache invalidate之后,并把invalidate message放置到invalide queue,以待之后處理。

并發(fā)原理系列一:MESI與內存屏障

但是這種方法會使得我們之前的內存屏障的例子也失效,主要是因為在cpu1收到cpu0關于a的invalidate消息之后直接ack,而沒有真正invalidate cache,導致退出循環(huán)之后發(fā)現a是有效的,執(zhí)行assert(a==1)失敗

  1. void foo(void) 
  2. a = 1; 
  3. smp_mb(); 
  4. b = 1; 
  5. void bar(void) 
  6. while (b == 0) continue
  7. smp_mb(); 
  8.                          
  9. assert(a == 1); 

在assert之前插入內存屏障,作用是把invalidate queue標記下,在讀取下面的數據的時候,譬如a的時候會先把invalidate queue中的消息都處理掉,這里的話會使得a失效而去cpu0獲取最新的數據。

進而我們知道smp_mb有兩個作用,1,標記store buffer,在處理之后的寫請求之前需要把store buffer中的數據apply到cache,2,標記invalidate queue,在加載之后的數據之前把invalidate queue中的消息都處理掉

進而我們再觀察上面的例子,我們發(fā)現,在foo中我們不需要處理invalidate queue,而在bar中,我們不需要處理store buffer,我們可以使用一種更弱的內存屏障來修改上例讓我們程序的性能更高,smp_wmb寫屏障,只會標記store buffer,smp_rmb讀屏障,只會標記invalidate queue,代碼如下:

  1. void foo(void) 
  2. a = 1; 
  3. smp_wmb(); 
  4. b = 1; 
  5. void bar(void) 
  6. while (b == 0) continue
  7. smp_rmb(); 
  8. assert(a == 1); 

本文基本是對http://www.puppetmastertrading.com/images/hwViewForSwHackers.pdf的理解與翻譯。

MESI緩存一致性協(xié)議,能保證緩存和內存數據一致

volatile表示不使用寄存器的值,每次都從內存讀(不包括緩存)

dma越過cpu修改內存,會影響MESI

補充MESI:

緩存一致性協(xié)議給緩存行(通常為64字節(jié))定義了個狀態(tài):獨占(exclusive)、共享(share)、修改(modified)、失效(invalid),用來描述該緩存行是否被多處理器共享、是否修改。所以緩存一致性協(xié)議也稱MESI協(xié)議。

  • 獨占(exclusive):僅當前處理器擁有該緩存行,并且沒有修改過,是最新的值。
  • 共享(share):有多個處理器擁有該緩存行,每個處理器都沒有修改過緩存,是最新的值。
  • 修改(modified):僅當前處理器擁有該緩存行,并且緩存行被修改過了,一定時間內會寫回主存,會寫成功狀態(tài)會變?yōu)镾。
  • 失效(invalid):緩存行被其他處理器修改過,該值不是最新的值,需要讀取主存上最新的值。
  • 協(xié)議協(xié)作如下:
  • 一個處于M狀態(tài)的緩存行,必須時刻監(jiān)聽所有試圖讀取該緩存行對應的主存地址的操作,如果監(jiān)聽到,則必須在此操作執(zhí)行前把其緩存行中的數據寫回CPU。
  • 一個處于S狀態(tài)的緩存行,必須時刻監(jiān)聽使該緩存行無效或者獨享該緩存行的請求,如果監(jiān)聽到,則必須把其緩存行狀態(tài)設置為I。
  • 一個處于E狀態(tài)的緩存行,必須時刻監(jiān)聽其他試圖讀取該緩存行對應的主存地址的操作,如果監(jiān)聽到,則必須把其緩存行狀態(tài)設置為S。
  • 當CPU需要讀取數據時,如果其緩存行的狀態(tài)是I的,則需要從內存中讀取,并把自己狀態(tài)變成S,如果不是I,則可以直接讀取緩存中的值,但在此之前,必須要等待其他CPU的監(jiān)聽結果,如其他CPU也有該數據的緩存且狀態(tài)是M,則需要等待其把緩存更新到內存之后,再讀取。
  • 當CPU需要寫數據時,只有在其緩存行是M或者E的時候才能執(zhí)行,否則需要發(fā)出特殊的RFO指令(Read Or Ownership,這是一種總線事務),通知其他CPU置緩存無效(I),這種情況下會性能開銷是相對較大的。在寫入完成后,修改其緩存狀態(tài)為M。

另外MESI協(xié)議為了提高性能,引入了Store Buffe和Invalidate Queues,還是有可能會引起緩存不一致,還會再引入內存屏障來確保一致性

存儲緩存(Store Buffe)

也就是常說的寫緩存,當處理器修改緩存時,把新值放到存儲緩存中,處理器就可以去干別的事了,把剩下的事交給存儲緩存。

失效隊列(Invalidate Queues)

處理失效的緩存也不是簡單的,需要讀取主存。并且存儲緩存也不是無限大的,那么當存儲緩存滿的時候,處理器還是要等待失效響應的。為了解決上面兩個問題,引進了失效隊列(invalidate queue)。處理失效的工作如下:

  • 收到失效消息時,放到失效隊列中去。
  • 為了不讓處理器久等失效響應,收到失效消息需要馬上回復失效響應。
  • 為了不頻繁阻塞處理器,不會馬上讀主存以及設置緩存為invlid,合適的時候再一塊處理失效隊列。

MESI和CAS關系

在x86架構上,CAS被翻譯為”lock cmpxchg...“,當兩個core同時執(zhí)行針對同一地址的CAS指令時,其實他們是在試圖修改每個core自己持有的Cache line,

假設兩個core都持有相同地址對應cacheline,且各自cacheline 狀態(tài)為S, 這時如果要想成功修改,就首先需要把S轉為E或者M, 則需要向其它core invalidate 這個地址的cacheline,則兩個core都會向ring bus發(fā)出 invalidate這個操作, 那么在ringbus上就會根據特定的設計協(xié)議仲裁是core0,還是core1能贏得這個invalidate, 勝者完成操作, 失敗者需要接受結果, invalidate自己對應的cacheline,再讀取勝者修改后的值, 回到起點.。

對于我們的CAS操作來說, 其實鎖并沒有消失,只是轉嫁到了ring bus的總線仲裁協(xié)議中. 而且大量的多核同時針對一個地址的CAS操作會引起反復的互相invalidate 同一cacheline, 造成pingpong效應, 同樣會降低性能(參考[9])。當然如果真的有性能問題,我覺得這可能會在ns級別體現了,一般的應用程序中使用CAS應該不會引起性能問題。

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2010-04-27 09:17:23

內存屏障JVM

2019-12-10 14:51:00

CPU緩存內存

2019-11-12 14:40:43

CPU緩存內存

2019-10-30 09:56:56

內存屏障變量

2020-11-30 16:01:03

Semaphore

2020-12-09 08:21:47

編程Exchanger工具

2020-12-04 19:28:53

CountDownLaPhaserCyclicBarri

2020-12-03 11:15:21

CyclicBarri

2021-07-03 17:44:34

并發(fā)高并發(fā)原子性

2025-06-12 03:25:00

2017-09-04 15:15:48

Linux內核內存屏障

2025-09-08 04:11:00

2025-03-27 09:14:51

2011-07-11 18:02:50

java

2011-07-11 18:10:28

java

2010-09-25 15:19:01

2013-06-19 10:55:40

Disruptor并發(fā)框架

2023-10-27 07:47:58

Java語言順序性

2023-04-06 00:15:03

JavaReentrantL線程

2023-09-12 13:48:47

點贊
收藏

51CTO技術棧公眾號