伪共享
計算機分為CPU、內存、硬盤等部分,我們運行中的程序也就是進程運行在內存中(當內存不足時可能被交換到位于硬盤的swap區)。
進程中包括數據區、代碼區,CPU將代碼指令和數據通過總線獲取到CPU中進行執行。
CPU中存在很多寄存器,CPU到寄存器的存取速度很快,但是寄存器的空間很小。內存的存取速度比寄存器慢,但是擁有更大的空間。
當內存訪問速度遠遠落后于CPU時,將導致系統執行速度受到內存速度的瓶頸限制,因此為了緩沖兩者間的速度差異,在直接增加了一些緩存。
緩存利用了局部性原理: CPU訪問存儲器時,無論是存取指令還是存取數據,所訪問的存儲單元都趨于聚集在一個較小的連續區域中。局部性又分為了時間局部性(一個數據在不久后還可能會被訪問)、空間局部性(一個數據附近的數據在不久后可能會被訪問到)、順序局部性(大部分代碼是順序執行的)。這樣在CPU和內存間增加緩存,來緩存剛使用到的數據,緩存的速度更快、相應的比內存空間小,能夠提高系統的性能。當前一般的計算機架構從CPU到外層依次為CPU-> 寄存器 -> L1 Cache -> C2 Cache -> L3Cache -> 主內存 -> 磁盤。越靠近CPU的越小速度越快。CPU訪問主內存的時間大概在80ns左右,而L1 Cache只需要約1ns。
CPU依靠多核來提高執行能力后,每個核都有自己的一級、二級緩存,這樣有帶來了緩存一致性問題,一些CPU通過緩存一致性協議來同步緩存間的數據的一致性,應用程序可以通過一些內存屏障等機制進行數據同步。
需要注意的是緩存中不存儲單個的項目,例如,不存儲單個值、單個指針,緩存由緩存行(Cache Line)組成(通常是64bytes),并且和內存中的一個位置對應。Java中一個long值占用8bytes,所以一個緩存行可以放8個long變量。
所以當我們訪問一個long數組時,獲取一個值后,其相鄰的值也被緩存到就近的緩存中,當我們迭代數組的時候就會很快速。
但是有一些情況可能比較糟糕,會造成緩存miss或頻繁緩存失效。
假設有一個head引用,并且在其之后有另一個tail引用。現在把head load到緩存的時候,tail也會被緩存起來。但是現在問題是head是有消費者線程控制寫入的,tail是由生產者控制寫入的,它們兩個可能不是一個線程,當更新head的時候,緩存值被更新、內存被更新,并且其他包含這個head的緩存行也要被失效來保證一致性。這樣情況導致的緩存失效問題叫做偽共享(false sharing),因為每次訪問head的時候,也會得到tail。
解決方式
防止其他數據導致緩存失效的問題常用增加padding,叫做緩存行填充的方式來解決,例如在前后加上無用的數據。
|
? 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為平臺上, 一個對象 有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); } } |
?
值得注意的是,這些優化細節比較底層,并不一定能夠起作用,面對不同的編譯器、運行時和CPU都有可能有不同的優化。
關鍵點:
局部性。緩存。緩存一致性。
參考
- http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-why-its-so-fast_22.html
from:?https://liuzhengyang.github.io/2017/04/13/false-sharing/?
總結
- 上一篇: 伪共享(false sharing),并
- 下一篇: 一篇对伪共享、缓存行填充和CPU缓存讲的