Disruptor高性能之道-局部性原理與緩沖行填充細節(jié)補充
在之前的文章中,我們討論了Disruptor高性能實現機制中的:
RingBuffer環(huán)形隊列及內存預加載 緩存行填充避免偽共享
本文對之前沒有講到的細節(jié)進行補充。
對于數組元素預加載的補充解釋
private?void?fill(EventFactory?eventFactory){
??for?(int?i?=?0;?i???????entries[BUFFER_PAD?+?i]?=?eventFactory.newInstance();
??}
}
一次性填充滿整個數組,這樣做是一個比較有技巧的做法,Disruptor通過填充滿數組,在運行時改變對象的值來達到防止Java垃圾回收(GC)產生的系統(tǒng)開銷。
換句話說就是它不需要垃圾回收。
Disruptor是如何通過位運算提升取模效率的?
我們已經知道,RingBufferSize為2的N次方時,可以通過位于運算提升取模效率,公式為:
?seq & (ringBufferSize - 1) == index
?
即:當前event的sequence與RingBufferSize-1的差進行位于運算,就等價于sequence Mod RingBufferSize,但是效率更高。
在Disruptor的源碼中具體是如何利用該機制的?
????@Override
????public?E?get(long?sequence)
????{
????????return?elementAt(sequence);
????}
?Disruptor通過get(sequence)從RingBuffer中取出下一個可用的sequence位于RingBuffer中的下標,具體實現在elementAt方法中。
?
//?com.lmax.disruptor.RingBufferFields#elementAt
protected?final?E?elementAt(long?sequence)?{
??return?(E)?UNSAFE.getObject(entries,?REF_ARRAY_BASE?+?((sequence?&?indexMask)?<}
?可以看到elementAt是通過UNSAFE直接調用底層方法getObject,通過遞增序列號獲取與序列號對應的數組元素。
?
緩存行填充與局部性原理
我們知道Disruptor是通過緩存行填充避免了偽共享問題。
實際上這與 “局部性原理” 息息相關。
?解釋下什么叫做:局部性原理。
程序的局部性原理指的是在一段時間內程序的執(zhí)行會限定在一個局部范圍內。這里的“局部性”可以從兩個方面來理解,一個是時間局部性,另一個是空間局部性。
時間局部性指的是程序中的某條指令一旦被執(zhí)行,不久之后這條指令很可能再次被執(zhí)行;如果某條數據被訪問,不久之后這條數據很可能再次被訪問。
而空間局部性是指某塊內存一旦被訪問,不久之后這塊內存附近的內存也很可能被訪問。
?
CPU緩存讀寫就利用了局部性原理。
當CPU從主內存加載數據A時,它會將數據A緩存至CPU的高速緩存cache中。除了A會被緩存,A附近的數據也會被緩存。
根據局部性原理分析,由于A會被訪問,那么A周圍的其他數據也很有可能會被訪問,如果一并加載則會提升程序的性能。
但是由于多核CPU同時修改同一緩存行,導致緩存行失效后重新加載主內存,因此出現了偽共享的問題。
再次分析Disruptor對變量的緩存行填充原理
首先看一下Disruptor中對 INITIAL_CURSOR_VALUE 的特殊處理。
public?final?class?RingBuffer?extends?RingBufferFields?implements?Cursored,?EventSequencer,?EventSink
{
????public?static?final?long?INITIAL_CURSOR_VALUE?=?Sequence.INITIAL_VALUE;
????protected?long?p1,?p2,?p3,?p4,?p5,?p6,?p7;
RingBuffer繼承于RingBufferField
abstract?class?RingBufferFields?extends?RingBufferPad
RingBufferFields繼承于RingBufferPad
abstract?class?RingBufferPad
{
????protected?long?p1,?p2,?p3,?p4,?p5,?p6,?p7;
}
那么我們就知道,在INITIAL_CURSOR_VALUE前后各填充了7個long型變量。
前面的 7 個來自繼承的 RingBufferPad 類,后面的 7 個則是直接定義在 RingBuffer 類里 面。
「這14個變量沒有任何實際的用途。既不會去讀也不會去寫他們?!?/strong>

可以看到,常量INITIAL_CURSOR_VALUE前后各填充了7個long型變量,無論CPU高速緩存如何加載緩存行(一個緩存行8個long型長度),整個緩存行都沒有會發(fā)生變更的數據,這個8個long類型的緩存行無論如何加載上面的內存行,都能夠讀到常量,且不會加載除了常量的其他變量。
?而INITIAL_CURSOR_VALUE是一個常量,也不會進行修改。所以一旦它被加載到CPU Cache 之后,只要被頻繁地讀取訪問,就不會再被換出 Cache 了。這也就意味著對于這個值的讀取速度,會是「一直是 CPU Cache 的訪問速度,而不是內存的訪問速度」。
?
這有效地解決了偽共享的問題。
