美文网首页
解读Disruptor系列--解读源码(4)之RingBuffe

解读Disruptor系列--解读源码(4)之RingBuffe

作者: coder_jerry | 来源:发表于2019-09-30 11:27 被阅读0次

    今天和大家聊一聊Disruptor中的RingBuffer。代码版本基于3.3.6,逻辑和3.4.x变化不大。

    0x01 Disruptor中的RingBuffer

    RingBuffer在Disruptor早期功能比较多,承载着数据存储、生产消费的数据交换等任务。现在只保留了存储的能力,像发布数据这些功能也只是通过调用Sequencer去实现的。在RingBuffer中其实看不到这个"Buffer"为何是"Ring",可以看看我之前关于生产者的文章了解下。
    这里我们可以把Disruptor中的RingBuffer简单地理解为一个经过特殊优化的数组。
    这个“特殊的数组”的特别之处在于:

    1. 尽可能消除缓存的伪共享问题;
    2. 使用数组存储,预先分配(尽可能)连续的内存地址,非常适合FIFO的时序消息特性,充分利用CPU Cache预取能力;
    3. 对象重用,减少不必要的GC;

    0x02 实现细节

    0x02.1 解决伪共享问题

    伪共享简介:计算机缓存是以缓存行(cache line)大小从内存拉数据并存储。缓存行最常见大小是64个字节。当同一缓存行内的不同变量被不同,就会无意中影响彼此的性能,这就是伪共享。伪共享常被称做无声的性能杀手,因为从代码中很难看清楚是否会出现伪共享。

    RingBuffer类图

    类图最左侧的RingBufferPad、RingBufferFields就是为了解决伪共享问题的。在RingBuffer的生命周期中,RingBufferFields中的属性会被频繁访问,为了解决缓存的伪共享问题,需要对每个缓存行进行填充。这种形式在Disruptor中经常使用。下表是RingBuffer实例属性。可以发现,不管缓存行中从哪个位置加载代表RingBuffer实例的数据,实际使用的属性sequencer、bufferSize、entries、indexMask会被加载到一或两个缓存行中,不会受到非RingBuffer属性外的干扰。

    /*
     * 填充辅助类,为解决缓存的伪共享问题,需要对每个缓存行(64B)进行填充
     */
    abstract class RingBufferPad
    { // https://github.com/LMAX-Exchange/disruptor/issues/167
        /*
        RingBufferFields中的属性被频繁读取,这里的属性是为了避免RingBufferFields遇到伪共享问题
         */
        protected long p1, p2, p3, p4, p5, p6, p7;
    }
    
    abstract class RingBufferFields<E> extends RingBufferPad {
        private static final int BUFFER_PAD; // 用于在数组中进行缓存行填充的空元素个数
        private static final long REF_ARRAY_BASE; // 内存中引用数组的开始元素基地址,是数组开始的地址+BUFFER_PAD个元素的偏移量之和,后续元素的内存地址需要在此基础计算地址
        private static final int REF_ELEMENT_SHIFT; // 引用元素的位移量,用于计算BUFFER_PAD偏移量,基于位移计算比乘法运算更高效
        private static final Unsafe UNSAFE = Util.getUnsafe(); // 上面的变量都是为了UNSAFE的操作
    
        static {
            final int scale = UNSAFE.arrayIndexScale(Object[].class); // arrayIndexScale获取数组中一个元素占用的字节数,不同JVM实现可能有不同的大小
            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; // BUFFER_PAD=32 or 16,为什么是128呢?是为了满足处理器的缓存行预取功能(Adjacent Cache-Line Prefetch)
            // https://github.com/LMAX-Exchange/disruptor/issues/158
            // https://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers
            // Including the buffer pad in the array base offset
            // BUFFER_PAD << REF_ELEMENT_SHIFT 实际上是BUFFER_PAD * scale的等价高效计算方式
            REF_ARRAY_BASE = UNSAFE.arrayBaseOffset(Object[].class) + (BUFFER_PAD << REF_ELEMENT_SHIFT);
        }
    
        private final long indexMask; // 用于进行 & 位与操作,实现高效的模操作
        private final Object[] entries;
        protected final int bufferSize;
        protected final Sequencer sequencer; // 生产者序列号
        // 省略...
    }
    public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E>
    {
        public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE; // 游标初始值 -1
        protected long p1, p2, p3, p4, p5, p6, p7;
    }
    
    属性类型 属性名 字节数
    long p7 8
    long p6 8
    long p5 8
    long p4 8
    long p3 8
    long p2 8
    long p1 8
    ref sequencer 4/8
    int bufferSize 4
    ref entries 4/8
    long indexMask 8
    long p7 8
    long p6 8
    long p5 8
    long p4 8
    long p3 8
    long p2 8
    long p1 8

    0x02.2 对象复用与缓存预取

    使用数组而非链表,可以通过数组连续内存的特性最大化利用缓存行。但是数组里存储的一般是对象的引用,所以提前初始化对象有两点好处,其一是避免频繁的创建销毁,减少young gc,其二是通过初始化所有对象,尽可能使对象内存连续,由于处理器通常开启了缓存预取机制(参见Intel缓存预取文章),这样就增加了缓存效率,降低了整体时延。Disruptor使用的事件对象在RingBuffer中不断往前推进,缓存可能在使用前就将数据准备好了。

    0x03 总结

    RingBuffer的设计秉承了Disruptor的一贯思想,为了追求极致性能,不得不在软件层做出对硬件层的妥协。

    更多的源码注释参考

    参考资料

    1. 细说Cache-L1/L2/L3/TLB
    2. Why software developers should care about CPU caches
    3. Optimizing Application Performance on Intel® Core™ Microarchitecture Using Hardware-Implemented Prefetchers
    4. CPU Cache Wiki
    5. Locality of reference Wiki

    相关文章

      网友评论

          本文标题:解读Disruptor系列--解读源码(4)之RingBuffe

          本文链接:https://www.haomeiwen.com/subject/unufpctx.html