所属文集:一起掌握并发
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的高性能操作总结。
-
Single Writer Principle(单写原则)
MP是多写,没有这个原则。 -
lazy set
- 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 来保证;
- 向buffer中写数据 StoreStore,保证写入顺序
/**
* 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);
}
- 从buffer中读数据 LoadLoad,保证读取最新数据。
/**
* 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);
- 大量的位运算
在前面中也看到了,通过&运算得到数组的下标,<< 计算数组的偏移地址,再利用unsafe进行设置等操作。举个例子%运算耗时是&的两倍。
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
- 伪共享
Java并发编程系列:漫谈伪共享
感谢你们:
高性能SPSC无锁队列设计之路
Netty中Queue的实现
新版Netty中,使用JCTools中的容器
Netty解读:Jctools高性能无锁队列源码分析
AtomicLong.lazySet是如何工作的?
Java中的指针:Unsafe类
思考Java中的指针:Unsafe类中的这段话:
在Java中使用本地内存有它的意义。从延迟的角度来说,直接访问本地内存不会比访问Java堆快。这个结论其实是有道理的,因为跨越JVM屏障肯定是有开销的。这样的结论对使用本地还是堆的ByteBuffer同样适用。使用本地ByteBuffer的速度提升不在于访问这些内存,而是它可以直接与操作系统提供的本地IO进行操作。
网友评论