disruptor RingBuffer初始化與生產者事件產生

在Disruptor中,爲了防止僞共享導致的性能降低,所有元素都會在前後儘量填充64個字節以保證在cpu以64字節緩存數據的時候,在緩存行中,都只會有自己所需要的數據,不會導致緩衝行的更新影響到別的cpu,以空間換時間,保證性能的提升。

在RingBuffer的初始化中,在其父類RingBufferFields的靜態代碼塊及構造方法中,也可以看到爲了消除僞共享而進行的內存填充。

static
{
    final int scale = UNSAFE.arrayIndexScale(Object[].class);
    if (4 == scale)
    {
        REF_ELEMENT_SHIFT = 2;
    }
    else if (8 == scale)
    {
        REF_ELEMENT_SHIFT = 3;
    }
    else
    {
        throw new IllegalStateException("Unknown pointer size");
    }
    BUFFER_PAD = 128 / scale;
    // Including the buffer pad in the array base offset
    REF_ARRAY_BASE = UNSAFE.arrayBaseOffset(Object[].class) + 128;
}

在RingBuffer存放具體的事件數組entries中,會在數組前後分別填充128個字節(尤其是首尾的元素),同時根據該jvm中數組單個元素的字節大小來確定填充的元素個數,在具體的申請數組空間的時候會用到。

同時,當通過Unsafe來通過偏移量來獲取具體數組元素的時候,在起點需要偏移量需要先前進128字節,纔是真正的第一個事件的具體位置。

 

再看RingBufferFields的構造方法。

RingBufferFields(
        EventFactory<E> eventFactory,
        Sequencer sequencer)
{
    this.sequencer = sequencer;
    this.bufferSize = sequencer.getBufferSize();

    if (bufferSize < 1)
    {
        throw new IllegalArgumentException("bufferSize must not be less than 1");
    }
    if (Integer.bitCount(bufferSize) != 1)
    {
        throw new IllegalArgumentException("bufferSize must be a power of 2");
    }

    this.indexMask = bufferSize - 1;
    this.entries = new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD];
    fill(eventFactory);
}

    private void fill(EventFactory<E> eventFactory)
    {
        for (int i = 0; i < bufferSize; i++)
        {
            entries[BUFFER_PAD + i] = eventFactory.newInstance();
        }
    }

由於RingBuffer是一個環形隊列,其具體的定位實則是通過序列號與總長度取模,由此,當長度爲2的冪次方後,只需要將序列號與總長度減一進行相與,即可快速得到其應該在這個數組中的位置。在根據隊列長度和填充元素的個數申請完數組之後,將會通過fill()方法一次性將數組全部填充,後續事件的增加修改只會直接修改數組中事件的字段,不會再涉及內存的申請與回收。

protected final E elementAt(long sequence)
{
    return (E) UNSAFE.getObject(entries, REF_ARRAY_BASE + ((sequence & indexMask) << REF_ELEMENT_SHIFT));
}

elementAt()方法實現了根據具體的序列號來獲取環形隊列的具體元素,根據之前移動了128字節作爲起始位置,序列號與隊列長度減一相與的結果爲隊列的具體位置下標,根據單個元素所佔字節數,來在連續內存上獲取具體的位置。

 

具體的序列號實現類,也是通過內存填充來實現消除僞共享的。

class LhsPadding
{
    protected long p1, p2, p3, p4, p5, p6, p7;
}

class Value extends LhsPadding
{
    protected volatile long value;
}

class RhsPadding extends Value
{
    protected long p9, p10, p11, p12, p13, p14, p15;
}

實際序列號value將會前後分別填充7個long 類型分別是56字節,以保證前後加上value實例能夠填滿64字節,消除僞共享。

 

生產者是如何產生事件的?

當生產者準備通過publishEvnet()方法產生事件的時候,首先根據,會根據sequencer來通過next()方法來獲取將事件所存放的下標。

單個消費者的時候next()方法由SingleProducerSequencer來實現。

public long next(int n)
{
    if (n < 1 || n > bufferSize)
    {
        throw new IllegalArgumentException("n must be > 0 and < bufferSize");
    }

    long nextValue = this.nextValue;

    long nextSequence = nextValue + n;
    long wrapPoint = nextSequence - bufferSize;
    long cachedGatingSequence = this.cachedValue;

    if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
    {
        cursor.setVolatile(nextValue);  // StoreLoad fence

        long minSequence;
        while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue)))
        {
            LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin?
        }

        this.cachedValue = minSequence;
    }

    this.nextValue = nextSequence;

    return nextSequence;
}

其實現比較簡單,直接將當前序列號加上就可以返回,需要注意的是,如果是其產生事件的並還未消費的數量已經超過了隊列的長度將會進行自旋,直到有位置可以存放新的事件。

而多生產者的情況下,則是在其基礎上增加了序列號的cas操作,確保能夠獲得其序列號不會和別的生產者衝突。

在獲得了其序列號後,只需要根據上方的elementAt()方法獲取數組上具體的內存空間,將新的事件覆蓋在這個位置上即可。

在完成事件的寫入,通過生產者的publish()通知消費者進行消費。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章