Cache一致性協議與偽共享問題

2022-09-10 20:06:11 字數 4738 閱讀 9126

在說偽共享問題之前,有必要聊一聊什麼是cache一致性協議

時間區域性性:如果乙個資訊項正在被訪問,那麼在近期它很可能還會被再次訪問

比如迴圈、方法的反覆呼叫等

空間區域性性:如果乙個儲存器的位置被引用,那麼將來他附近的位置也會被引用

比如順序結構、陣列

cpu在摩爾定律的指導下以每18個月翻一番的速度在發展,然而記憶體和硬碟的發展速度遠遠不及cpu。為了解決這個問題,cpu廠商在cpu中內建了少量的快取記憶體cache,以解決訪存速度和cpu運算速度之間不匹配的問題

cpu和cache交換資料以為單位。cache與主存以為單位,乙個快取行(cache line)對應乙個主存塊

cache不命中時

cache和主存的對映方式(三種):直接對映、全相聯對映、組相聯對映

如果是單cpu結構,這麼執行沒有其他問題。但是現代系統往往包含多個cpu,每個cpu都有各自的cache。多核cpu的情況下有多個一級快取,如何保證快取內部資料的一致性,本質上就是為了防止資料的髒讀,這裡就引出了cache一致性協議——mesi。注意,這並不是唯一的快取一致性協議,還有其他協議如mosei(相對於mesi多引入了乙個owned狀態,並重新定義了s狀態)、mesif(配合numa架構)等,這裡不多介紹

mesi(modified exclusive shared or invalid),也稱伊利諾斯協議,是一種廣泛使用的、支援寫回策略的快取一致性協議。mesi協議其實就是使用4種狀態來標記各個快取行(cache line)的狀態,而這些狀態英文首字母縮寫就構成了「mesi」

每個快取行都使用乙個狀態來標記,該狀態總共有4種,使用2bit進行儲存:

每個cpu都可以感知其他cpu的行為,比如讀、寫某個快取行,這就是嗅探機制,也稱監聽。所有的快取行(除了invalid狀態)都需要監聽自己和其他cpu對相應的快取行的讀寫操作,也稱觸發事件,從而根據觸發事件和自身狀態,進行狀態的轉換

觸發事件

描述本地讀取(local read)

本cpu讀取本cache的資料

本地寫入(local write)

本cpu向本cache寫入資料

遠端讀取(remote read)

其他cpu讀取它們各自cache的資料

遠端寫入(remote write)

其他cpu向它們各自cache寫入資料

下圖描述了當前快取行在不同觸發事件下的狀態切換:

下表是對上圖的乙個詳細解釋:

假設cpu0、cpu1、cpu2、cpu3中有乙個快取行(包含變數x)都是s狀態

此時cpu1要對變數x進行寫操作,這時候通過匯流排嗅探機制,cpu0、cpu2、cpu3中的快取行會置為i狀態(無效),然後給cpu1發響應,收到全部響應後cpu1會完成對變數x的寫操作,並更新cpu1內的快取行為m狀態,但不會將資料x同步到主存中

接著cpu0想要對變數x執行讀操作,卻發現本地快取行是i狀態,就會觸發cpu1去把快取行寫回到主存中,然後cpu0再去主存中同步最新的值

mesi協議解決了快取一致性的問題,但其中有乙個問題,那就是需要在等待其他處理器全部回覆後才能進行下一步操作,這種等待明顯是不能接受的,下面就繼續來看看如何解決處理器等待的問題

1、寫緩衝(store buffer)

實際cpu1在執行寫操作要更新快取行時,其實並不會乖乖地等待其他cpu的快取行狀態都置為i狀態,才去執行寫操作,這樣效率很低。實際做法是:每個cpu都引入乙個寫快取器,相當於在cpu和cache之間又加了一層buffer,在cpu執行寫操作時直接寫入寫緩衝,然後去忙其他事,等其他cpu的快取行都置為i後,cpu1才把buffer中的資料寫入到cache中

2、無效化佇列(invalidate queue)

引入寫緩衝後,cpu1就可以不用等待其他cpu中對應的快取行失效而去忙別的。不過其他cpu也不傻,實際上他們也不會真的把快取行置為i後,才給cpu1發響應。他們會寫入乙個無效化佇列,還沒把快取置為i狀態就傳送響應了

之後cpu會非同步掃瞄無效化佇列,將快取行置為i狀態。和寫緩衝不同的是,cpu1之後讀變數x時,會先查寫緩衝,再查cache。而cpu0要讀變數x時,不會先去檢查無效化佇列,所以存在髒讀的可能

總之,寫緩衝器解決了寫資料時要等待其他處理器響應的問題,無效化佇列解決了刪除資料的等待問題。不管是寫緩衝器還是無效化佇列,其實都是為了減少處理器的等待時間,採用了空間換時間的方式來實現命令的非同步處理

不過既然是非同步處理,就又會引發新的問題——記憶體重排序和記憶體可見性問題,這裡不過多贅述,請參考網上其他文章,之後博主也會跟進

看懂這篇,才能說了解併發底層技術

現代系統都會採用多級快取架構,l1-l3級快取,其中l3快取是所有cpu共享的乙個快取,但mesi的描述中並沒有涉及l3快取。其實上文提到的所有跟「主存」交換資料的地方,在l3快取存在的情況下,都應該替換為l3快取。比如我上一節舉的例子中,cpu0中某快取行是i,cpu1 中是m。當cpu0想到執行local read操作時,就會觸發cpu1中的快取寫入到主存中,然後cpu0從主存中取最新的快取行。其實這裡的描述是不準確的,因為由於l3快取的存在,這裡其實是直接從l3快取讀取快取行,而不直接訪問主存

個人認為是如果在描述mesi的狀態流轉時,如果引入l3快取,會使得描述過於複雜,因此一般的描述都會刻意忽略l3快取

由於記憶體和cache之間的交換單位是記憶體塊/快取行,因此如果訪問乙個變數,會將一整個記憶體塊讀入乙個快取行。但是如果多個執行緒訪問的變數不相同,且這些變數在記憶體中的位置臨近,那麼很可能在同乙個快取行中。在cache一致性協議(如mesi協議)的約束下,多個執行緒(cpu)在並行讀寫相對應的快取行會有限制,因此其他執行緒不得不去訪問低階別的cache甚至是主存,這會導致cache沒有起到真正的作用,程式效能下降

如圖,執行緒1訪問變數x,而執行緒2訪問變數y,而這兩個變數在記憶體中的位置臨近(在同乙個記憶體塊中),雖然執行緒都將該記憶體塊讀入到各自的工作記憶體(cache)中,但是在cache一致性協議的約束下,同一時間兩個執行緒很難自由地讀寫相同位置的快取行,那麼可能就會讓其中乙個執行緒去低階別的記憶體中讀寫資料,效能因此降低,這就是偽共享

一般位址連續的多個變數更可能被放在同乙個快取行中,例如建立陣列時,陣列中的多個元素更可能被放入同乙個快取行中

jdk8之前

jdk8之前,使用位元組填充的方式,即建立乙個變數時,使用填充字段填充該變數所在的快取行,從而避免多個變數被放入同乙個快取行中,如下:

public final static class filledlong
一般來說,快取行為64 byte,而經過填充的filledlong物件有7*8 byte=56 byte,而filledlong物件的物件頭也有8 byte,正好填滿乙個快取行

jdk8及之後

jdk8提供了乙個註解——sun.misc.contended,用於解決偽共享問題,上述**可以修改為如下:

@sun.misc.contended

public final static class filledlong

thread類中,也有這樣的字段,如下:

@sun.misc.contended("tlr")

long threadlocalrandomseed;

/** probe hash value; nonzero if threadlocalrandomseed initialized */

@sun.misc.contended("tlr")

int threadlocalrandomprobe;

/** secondary seed isolated from public threadlocalrandom sequence */

@sun.misc.contended("tlr")

int threadlocalrandomsecondaryseed;

但是,@contended註解只用於j**a核心類,而使用者類路徑下的類使用該註解,需要新增jvm引數-xx:-restrictcontended。填充寬度預設為128 byte,也可以自定義寬度,通過jvm引數-xx:contendedpaddingwidth

Cache與一致性

3 知識點摘記 3.2 編譯屏障和記憶體屏障 參考文獻 對於cache cache一致性 記憶體一致性 記憶體屏障 原子操作等話題,涉及到很多處理器體系結構的細節,比較難懂。本文不會系統的分析這些問題,一是水平有限,二是工作量太大,三是目前已經有相關的高質量書籍和網路資料。所以本文著力收集一些相關話...

Cache一致性協議之MESI

處理器上有一套完整的協議,來保證cache一致性。比較經典的cache一致性協議當屬mesi協議,奔騰處理器有使用它,很多其他的處理器都是使用它的變種。單核cache中每個cache line有2個標誌 dirty和valid標誌,它們很好的描述了cache和memory 記憶體 之間的資料關係 資...

Cache 快取一致性

就硬體而言,cpu 晶元 處理器 記憶體 匯流排 磁碟等等,構成了一台電腦,當電腦執行乙個程式的時候,需要從磁碟讀到主記憶體,主記憶體再到快取,最後交由cpu執行。隨著現在的多核處理器的發展,運算的速度是越來越快,但是在運算的同時,也要遇到快取一致性的問題,簡單來說,多核處理器,每個核上有個多個處理...