伪共享
計(jì)算機(jī)分為CPU、內(nèi)存、硬盤等部分,我們運(yùn)行中的程序也就是進(jìn)程運(yùn)行在內(nèi)存中(當(dāng)內(nèi)存不足時(shí)可能被交換到位于硬盤的swap區(qū))。
進(jìn)程中包括數(shù)據(jù)區(qū)、代碼區(qū),CPU將代碼指令和數(shù)據(jù)通過總線獲取到CPU中進(jìn)行執(zhí)行。
CPU中存在很多寄存器,CPU到寄存器的存取速度很快,但是寄存器的空間很小。內(nèi)存的存取速度比寄存器慢,但是擁有更大的空間。
當(dāng)內(nèi)存訪問速度遠(yuǎn)遠(yuǎn)落后于CPU時(shí),將導(dǎo)致系統(tǒng)執(zhí)行速度受到內(nèi)存速度的瓶頸限制,因此為了緩沖兩者間的速度差異,在直接增加了一些緩存。
緩存利用了局部性原理: CPU訪問存儲(chǔ)器時(shí),無論是存取指令還是存取數(shù)據(jù),所訪問的存儲(chǔ)單元都趨于聚集在一個(gè)較小的連續(xù)區(qū)域中。局部性又分為了時(shí)間局部性(一個(gè)數(shù)據(jù)在不久后還可能會(huì)被訪問)、空間局部性(一個(gè)數(shù)據(jù)附近的數(shù)據(jù)在不久后可能會(huì)被訪問到)、順序局部性(大部分代碼是順序執(zhí)行的)。這樣在CPU和內(nèi)存間增加緩存,來緩存剛使用到的數(shù)據(jù),緩存的速度更快、相應(yīng)的比內(nèi)存空間小,能夠提高系統(tǒng)的性能。當(dāng)前一般的計(jì)算機(jī)架構(gòu)從CPU到外層依次為CPU-> 寄存器 -> L1 Cache -> C2 Cache -> L3Cache -> 主內(nèi)存 -> 磁盤。越靠近CPU的越小速度越快。CPU訪問主內(nèi)存的時(shí)間大概在80ns左右,而L1 Cache只需要約1ns。
CPU依靠多核來提高執(zhí)行能力后,每個(gè)核都有自己的一級(jí)、二級(jí)緩存,這樣有帶來了緩存一致性問題,一些CPU通過緩存一致性協(xié)議來同步緩存間的數(shù)據(jù)的一致性,應(yīng)用程序可以通過一些內(nèi)存屏障等機(jī)制進(jìn)行數(shù)據(jù)同步。
需要注意的是緩存中不存儲(chǔ)單個(gè)的項(xiàng)目,例如,不存儲(chǔ)單個(gè)值、單個(gè)指針,緩存由緩存行(Cache Line)組成(通常是64bytes),并且和內(nèi)存中的一個(gè)位置對(duì)應(yīng)。Java中一個(gè)long值占用8bytes,所以一個(gè)緩存行可以放8個(gè)long變量。
所以當(dāng)我們訪問一個(gè)long數(shù)組時(shí),獲取一個(gè)值后,其相鄰的值也被緩存到就近的緩存中,當(dāng)我們迭代數(shù)組的時(shí)候就會(huì)很快速。
但是有一些情況可能比較糟糕,會(huì)造成緩存miss或頻繁緩存失效。
假設(shè)有一個(gè)head引用,并且在其之后有另一個(gè)tail引用。現(xiàn)在把head load到緩存的時(shí)候,tail也會(huì)被緩存起來。但是現(xiàn)在問題是head是有消費(fèi)者線程控制寫入的,tail是由生產(chǎn)者控制寫入的,它們兩個(gè)可能不是一個(gè)線程,當(dāng)更新head的時(shí)候,緩存值被更新、內(nèi)存被更新,并且其他包含這個(gè)head的緩存行也要被失效來保證一致性。這樣情況導(dǎo)致的緩存失效問題叫做偽共享(false sharing),因?yàn)槊看卧L問head的時(shí)候,也會(huì)得到tail。
解決方式
防止其他數(shù)據(jù)導(dǎo)致緩存失效的問題常用增加padding,叫做緩存行填充的方式來解決,例如在前后加上無用的數(shù)據(jù)。
|
? 1 2 3 |
? public long p1, p2, p3, p4, p5, p6, p7; private volatile long cursor; public long p8, p9, p10, p11, p12, p13, p14; |
?
在32為平臺(tái)上, 一個(gè)對(duì)象 有4bytes的MarkWord, 4bytes指向類,這樣加起來128bytes就能夠防止偽共享問題了。
在Disrutpor就是用了這一方式,
|
? 1 2 3 4 |
? public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E> { public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE; protected long p1, p2, p3, p4, p5, p6, p7; |
JDK7的LinkedTransferQueue中也用到了padding
|
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
? static final class PaddedAtomicReference<T> extends AtomicReference<T> { Object p0; Object p1; Object p2; Object p3; Object p4; Object p5; Object p6; Object p7; Object p8; Object p9; Object pa; Object pb; Object pc; Object pd; Object pe; PaddedAtomicReference(T r) { super(r); } } |
?
值得注意的是,這些優(yōu)化細(xì)節(jié)比較底層,并不一定能夠起作用,面對(duì)不同的編譯器、運(yùn)行時(shí)和CPU都有可能有不同的優(yōu)化。
關(guān)鍵點(diǎn):
局部性。緩存。緩存一致性。
參考
- http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-why-its-so-fast_22.html
from:?https://liuzhengyang.github.io/2017/04/13/false-sharing/?
總結(jié)
- 上一篇: 伪共享(false sharing),并
- 下一篇: 一篇对伪共享、缓存行填充和CPU缓存讲的