JCTools中的queue

2020-01-03  本文已影响0人  rock_fish

所属文集:一起掌握并发

Netty后来版本中使用了JCTools中的queue;

JDK自带的queue和JCTools的queue对比效果

100万数据
括号中的213是秒数。比如4901/s (213) 用时213秒,平均每秒钟处理4901条。
capacity 其值的大小对结果的影响是蛮大的

Producer Consumer capacity LinkedBlockingQueue ArrayBlockingQueue MpscLinkedAtomicQueue MpscChunkedArrayQueue MpscArrayQueue
128 1 128 4901/s (213) 38518/s (27) 17702070/s (0) 153652/s (6) 161788/s (6)
128 1 256 10210/s (102) 64686/s (16) 21769821/s (0) 154687/s (6) 181169/s (5)
128 1 512 26195/s (40) 99332/s (10) 20426455/s (0) 184272/s (5) 193574/s (5)
128 1 1024 160711/s (6) 146096/s (7) 22314536/s (0) 108893/s (9) 213303/s (4)
256 1 128 2518/s (416) 18079/s (57) 4699428/s (0) 55165/s (19) 91258/s (11)
256 1 256 5418/s (193) 27639/s (37) 20966414/s (0) 56481/s (18) 74522/s (14)
256 1 512 17290/s (60) 41326/s (25) 21005054/s (0) 68642/s (15) 111464/s (9)
256 1 1024 27646/s (37) 81722/s (12) 20153991/s (0) 52241/s (20) 84719/s (12)

MPSC的高性能操作总结。
  1. lazySet是使用Unsafe.putOrderedObject方法,会前置一个store-store barrier(在当前的硬件体系下或者是no-op或者非常轻),而不是store-load barrier, store-load barrier较慢,总是用在volatile的写操作上。在操作序列Store1; StoreStore;Store2中,Store1的数据会在Store2和后续写操作之前对其它处理器可见。换句话说,就是保证了对其它数据可见的写的顺序。
    如果只有一个线程写我们就用不着store-load barrier,lazySet和volatile set在单写原则下面是等价的。 这种性能提升是有代价的,也就是写后结果并不会被其他线程看到,甚至是自己的线程,通常是几纳秒后被其他线程看到,lazySet的写在实践上来延迟是纳秒级,这个时间比较短,所以代价可以忍受。 类似Unsafe.putOrderedObject还有unsafe.putOrderedLong等方法,unsafe.putOrderedLong比使用 volatile long要快3倍左右,store-store的劣势是纳秒级的延迟。

总结:在MPSC的场景下,写的时候只需通过 StoreStore 保证写的顺序不乱,因为当前线程只负责写进去就行了,不需要读取(可见)这个最新值,
其他线程在需要读取的时候,在读取的操作中 加入load barrier 来保证;

    /**
     * An ordered store(store + StoreStore barrier) of an element to a given offset
     */
    public static <E> void soElement(E[] buffer, long offset, E e)
    {
        UNSAFE.putOrderedObject(buffer, offset, e);
    }
    /**
     * A volatile load (load + LoadLoad barrier) of an element from a given offset.
     */
    @SuppressWarnings("unchecked")
    public static <E> E lvElement(E[] buffer, long offset)
    {
        return (E) UNSAFE.getObjectVolatile(buffer, offset);
    }

扩展知识点:unsafe中提供的读写操作:

//A volatile load (load + LoadLoad barrier) of an element from a given offset.
//从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
public native Object getObjectVolatile(Object o, long offset);

//存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
public native void putObjectVolatile(Object o, long offset, Object x);
//有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效
//An ordered store(store + StoreStore barrier) of an element to a given offset
public native void putOrderedObject(Object o, long offset, Object x);

-----------------
//A plain store (no ordering/fences) of an element to a given offset
//获得给定对象的指定地址偏移量的值,与此类似操作还有:getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//给定对象的指定地址偏移量设值,与此类似操作还有:putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
public static long calcElementOffset(long index, long mask)
    {
        return REF_ARRAY_BASE + ((index & mask) << REF_ELEMENT_SHIFT);
    }
//index & mask 等同于 % 取余 ;取4的余数,mask =(4-1) 
//REF_ELEMENT_SHIFT = 2 ;
//<< REF_ELEMENT_SHIFT : 左移2 等同于 * 4
感谢你们:

高性能SPSC无锁队列设计之路
Netty中Queue的实现
新版Netty中,使用JCTools中的容器
Netty解读:Jctools高性能无锁队列源码分析
AtomicLong.lazySet是如何工作的?
Java中的指针:Unsafe类

思考Java中的指针:Unsafe类中的这段话:
在Java中使用本地内存有它的意义。从延迟的角度来说,直接访问本地内存不会比访问Java堆快。这个结论其实是有道理的,因为跨越JVM屏障肯定是有开销的。这样的结论对使用本地还是堆的ByteBuffer同样适用。使用本地ByteBuffer的速度提升不在于访问这些内存,而是它可以直接与操作系统提供的本地IO进行操作。

Java并发编程系列:漫谈伪共享
Java并发编程系列:CAS 详解
JVM系列:二、虚拟机中的对象布局

上一篇下一篇

猜你喜欢

热点阅读