CPU緩存行

CPU緩存 ? ??

? 執(zhí)行程序是靠運(yùn)行CPU執(zhí)行主存中代碼,但是CPU和主存的速度差異是非常大的,為了降低這種差距,在架構(gòu)中使用了CPU緩存救鲤,現(xiàn)在的計(jì)算機(jī)架構(gòu)中普遍使用了緩存,分為一級(jí)緩存秩冈,二級(jí)緩存本缠,還有一些具備三級(jí)緩存,我們可以看看這些組件的數(shù)據(jù)獲取訪問(wèn)速度入问。

從CPU到大約需要的 CPU 周期大約需要的時(shí)間

主存?約60-80納秒

QPI 總線傳輸

(between sockets, not drawn)

?約20ns

L3 cache約40-45 cycles,約15ns

L2 cache約10 cycles,約3ns

L1 cache約3-4 cycles,約1ns

寄存器1 cycle

如果要了解緩存搓茬,就必須要了解緩存的結(jié)構(gòu),以及多個(gè)CPU核心訪問(wèn)緩存存在的一些問(wèn)題和注意事項(xiàng)队他。

每個(gè)緩存里面都是由緩存行組成的卷仑,緩存系統(tǒng)中是以緩存行(cache line)為單位存儲(chǔ)的。緩存行是2的整數(shù)冪個(gè)連續(xù)字節(jié)麸折,一般為32-256個(gè)字節(jié)锡凝。最常見的緩存行大小是64個(gè)字節(jié)當(dāng)多線程修改互相獨(dú)立的變量時(shí)垢啼,如果這些變量共享同一個(gè)緩存行窜锯,就會(huì)無(wú)意中影響彼此的性能,這就是偽共享芭析。緩存行上的寫競(jìng)爭(zhēng)是運(yùn)行在SMP系統(tǒng)中并行線程實(shí)現(xiàn)可伸縮性最重要的限制因素锚扎。有人將偽共享描述成無(wú)聲的性能殺手,因?yàn)閺拇a中很難看清楚是否會(huì)出現(xiàn)偽共享馁启。

偽共享問(wèn)題

圖中說(shuō)明了偽共享的問(wèn)題驾孔。在核心1上運(yùn)行的線程想更新變量X,同時(shí)核心2上的線程想要更新變量Y惯疙。不幸的是翠勉,這兩個(gè)變量在同一個(gè)緩存行中。每個(gè)線程都要去競(jìng)爭(zhēng)緩存行的所有權(quán)來(lái)更新變量霉颠。如果核心1獲得了所有權(quán)对碌,緩存子系統(tǒng)將會(huì)使核心2中對(duì)應(yīng)的緩存行失效。當(dāng)核心2獲得了所有權(quán)然后執(zhí)行更新操作蒿偎,核心1就要使自己對(duì)應(yīng)的緩存行失效朽们。這會(huì)來(lái)來(lái)回回的經(jīng)過(guò)L3緩存,大大影響了性能诉位。如果互相競(jìng)爭(zhēng)的核心位于不同的插槽骑脱,就要額外橫跨插槽連接,問(wèn)題可能更加嚴(yán)重不从。

緩存行帶來(lái)的鎖競(jìng)爭(zhēng)

處理器為了提高處理速度惜姐,不直接和內(nèi)存進(jìn)行通訊,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1,L2或其他)后再進(jìn)行操作,但操作完之后不知道何時(shí)會(huì)寫到內(nèi)存歹袁;如果對(duì)聲明了Volatile變量進(jìn)行寫操作坷衍,JVM就會(huì)向處理器發(fā)送一條Lock前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存条舔。但是就算寫回到內(nèi)存枫耳,如果其他處理器緩存的值還是舊的,再執(zhí)行計(jì)算操作就會(huì)有問(wèn)題孟抗,所以在多處理器下迁杨,為了保證各個(gè)處理器的緩存是一致的,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議凄硼,每個(gè)處理器通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了铅协,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài)摊沉,當(dāng)處理器要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候狐史,會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存里把數(shù)據(jù)讀到處理器緩存里。

? ? 當(dāng)多個(gè)線程對(duì)同一個(gè)緩存行訪問(wèn)時(shí)说墨,其中一個(gè)線程會(huì)鎖住緩存行骏全,然后操作,這時(shí)候其他線程沒辦法操作緩存行尼斧。

緩存行

需要注意姜贡,數(shù)據(jù)在緩存中不是以獨(dú)立的項(xiàng)來(lái)存儲(chǔ)的,如不是一個(gè)單獨(dú)的變量棺棵,也不是一個(gè)單獨(dú)的指針楼咳。緩存是由緩存行組成的,通常是64字節(jié)(譯注:這篇文章發(fā)表時(shí)常用處理器的緩存行是64字節(jié)的律秃,比較舊的處理器緩存行是32字節(jié))爬橡,并且它有效地引用主內(nèi)存中的一塊地址。一個(gè)Java的long類型是8字節(jié)棒动,因此在一個(gè)緩存行中可以存8個(gè)long類型的變量。

如果你訪問(wèn)一個(gè)long數(shù)組宾添,當(dāng)數(shù)組中的一個(gè)值被加載到緩存中船惨,它會(huì)額外加載另外7個(gè)。因此你能非陈粕拢快地遍歷這個(gè)數(shù)組粱锐。事實(shí)上,你可以非晨敢兀快速的遍歷在連續(xù)的內(nèi)存塊中分配的任意數(shù)據(jù)結(jié)構(gòu)怜浅。我在第一篇關(guān)于ring buffer的文章中順便提到過(guò)這個(gè),它解釋了我們的ring buffer使用數(shù)組的原因。

因此如果你數(shù)據(jù)結(jié)構(gòu)中的項(xiàng)在內(nèi)存中不是彼此相鄰的(鏈表恶座,我正在關(guān)注你呢)搀暑,你將得不到免費(fèi)緩存加載所帶來(lái)的優(yōu)勢(shì)。并且在這些數(shù)據(jù)結(jié)構(gòu)中的每一個(gè)項(xiàng)都可能會(huì)出現(xiàn)緩存未命中跨琳。

不過(guò)自点,所有這種免費(fèi)加載有一個(gè)弊端。設(shè)想你的long類型的數(shù)據(jù)不是數(shù)組的一部分脉让。設(shè)想它只是一個(gè)單獨(dú)的變量桂敛。讓我們稱它為head,這么稱呼它其實(shí)沒有什么原因溅潜。然后再設(shè)想在你的類中有另一個(gè)變量緊挨著它术唬。讓我們直接稱它為tail。現(xiàn)在滚澜,當(dāng)你加載head到緩存的時(shí)候粗仓,你也免費(fèi)加載了tail。

直到你意識(shí)到tail正在被你的生產(chǎn)者寫入博秫,而head正在被你的消費(fèi)者寫入潦牛。這兩個(gè)變量實(shí)際上并不是密切相關(guān)的,而事實(shí)上卻要被兩個(gè)不同內(nèi)核中運(yùn)行的線程所使用挡育。

設(shè)想你的消費(fèi)者更新了head的值巴碗。緩存中的值和內(nèi)存中的值都被更新了,而其他所有存儲(chǔ)head的緩存行都會(huì)都會(huì)失效即寒,因?yàn)槠渌彺嬷衕ead不是最新值了橡淆。請(qǐng)記住我們必須以整個(gè)緩存行作為單位來(lái)處理(譯注:這是CPU的實(shí)現(xiàn)所規(guī)定的,詳細(xì)可參見深入分析Volatile的實(shí)現(xiàn)原理)母赵,不能只把head標(biāo)記為無(wú)效逸爵。

現(xiàn)在如果一些正在其他內(nèi)核中運(yùn)行的進(jìn)程只是想讀tail的值,整個(gè)緩存行需要從主內(nèi)存重新讀取凹嘲。那么一個(gè)和你的消費(fèi)者無(wú)關(guān)的線程讀一個(gè)和head無(wú)關(guān)的值师倔,它被緩存未命中給拖慢了。

當(dāng)然如果兩個(gè)獨(dú)立的線程同時(shí)寫兩個(gè)不同的值會(huì)更糟周蹭。因?yàn)槊看尉€程對(duì)緩存行進(jìn)行寫操作時(shí)趋艘,每個(gè)內(nèi)核都要把另一個(gè)內(nèi)核上的緩存塊無(wú)效掉并重新讀取里面的數(shù)據(jù)。你基本上是遇到兩個(gè)線程之間的寫沖突了凶朗,盡管它們寫入的是不同的變量瓷胧。

這叫作“偽共享”(譯注:可以理解為錯(cuò)誤的共享),因?yàn)槊看文阍L問(wèn)head你也會(huì)得到tail棚愤,而且每次你訪問(wèn)tail搓萧,你也會(huì)得到head。這一切都在后臺(tái)發(fā)生,并且沒有任何編譯警告會(huì)告訴你瘸洛,你正在寫一個(gè)并發(fā)訪問(wèn)效率很低的代碼揍移。

避免偽共享

? 在Java中

? ? ? ? 你會(huì)看到Disruptor消除這個(gè)問(wèn)題,至少對(duì)于緩存行大小是64字節(jié)或更少的處理器架構(gòu)來(lái)說(shuō)是這樣的(譯注:有可能處理器的緩存行是128字節(jié)货矮,那么使用64字節(jié)填充還是會(huì)存在偽共享問(wèn)題),通過(guò)增加補(bǔ)全來(lái)確保ring buffer的序列號(hào)不會(huì)和其他東西同時(shí)存在于一個(gè)緩存行中羊精。


因此沒有偽共享,就沒有和其它任何變量的意外沖突囚玫,沒有不必要的緩存未命中喧锦。

?Java8實(shí)現(xiàn)字節(jié)填充避免偽共享?

? JVM參數(shù)??-XX:-RestrictContended?

? ?@Contended 位于 sun.misc 用于注解java 屬性字段,自動(dòng)填充字節(jié)抓督,防止偽共享


? 在C語(yǔ)言中

? ?避免偽共享燃少,編譯器會(huì)自動(dòng)將結(jié)構(gòu)體,字節(jié)補(bǔ)全和對(duì)其铃在,對(duì)其的大小最好是緩存行的長(zhǎng)度阵具。

? ?總的來(lái)說(shuō),結(jié)構(gòu)體實(shí)例會(huì)和它的最寬成員一樣對(duì)齊定铜。編譯器這樣做因?yàn)檫@是保證所有成員自對(duì)齊以獲得快速存取的最容易方法阳液。

從上面的情況可以看出,在設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)的時(shí)候揣炕,應(yīng)該盡量將只讀數(shù)據(jù)與讀寫數(shù)據(jù)分開帘皿,并具盡量將同一時(shí)間訪問(wèn)的數(shù)據(jù)組合在一起。這樣?CPU?能一次將需要的數(shù)據(jù)讀入畸陡。如:

?這樣的數(shù)據(jù)結(jié)構(gòu)就很不利鹰溜。

?在?X86?下,可以試著修改和調(diào)整它

CACHE_LINE_SIZE?– sizeof(int)+sizeof(name)*sizeof(name[0])%CACHE_LINE_SIZE看起來(lái)很不和諧丁恭,CACHE_LINE_SIZE表示高速緩存行為 64Bytes?大小曹动。?__align?用于顯式對(duì)齊。這種方式是使得結(jié)構(gòu)體字節(jié)對(duì)齊的大小為緩存行的大小

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牲览,一起剝皮案震驚了整個(gè)濱河市墓陈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌第献,老刑警劉巖跛蛋,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異痊硕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)押框,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門岔绸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事盒揉〗唬” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵刚盈,是天一觀的道長(zhǎng)羡洛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)藕漱,這世上最難降的妖魔是什么欲侮? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮肋联,結(jié)果婚禮上威蕉,老公的妹妹穿的比我還像新娘。我一直安慰自己橄仍,他們只是感情好韧涨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著侮繁,像睡著了一般虑粥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宪哩,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天娩贷,我揣著相機(jī)與錄音,去河邊找鬼斋射。 笑死育勺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的罗岖。 我是一名探鬼主播涧至,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼桑包!你這毒婦竟也來(lái)了南蓬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤哑了,失蹤者是張志新(化名)和其女友劉穎赘方,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弱左,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窄陡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拆火。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跳夭。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涂圆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出币叹,到底是詐尸還是另有隱情润歉,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布颈抚,位于F島的核電站踩衩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏贩汉。R本人自食惡果不足惜驱富,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雾鬼。 院中可真熱鬧萌朱,春花似錦、人聲如沸策菜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)又憨。三九已至翠霍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蠢莺,已是汗流浹背寒匙。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躏将,地道東北人锄弱。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像祸憋,于是被迫代替她去往敵國(guó)和親会宪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容