并发编程框架Disruptor之高性能设计(上)

简介: 并发编程框架Disruptor之高性能设计(上)

架构 UML

image.png

1 单线程写

Disruptor的RingBuffer, 之所以可以做到完全无锁,也是因为"单线程写",这是所有"前提的前提",离了这个前提条件,没有任何技术可以做到完全无锁。Redis、Netty等等高性能技术框架的设计都是这个核心思想。

2 系统内存优化-内存屏障

实现无锁,还需一个关键技术:内存屏障。

对应到Java语言,就是valotile变量与happens before语义。


参阅: 内存屏障 - Linux的smp_wmb()/smp_ rmb()

系统内核:比如Linux的kfifo:smp_ wmb(),无论是底层的读写

都是使用了Linux的smp_ wmb

https://github.com/opennetworklinux/linux-3.8.13/blob/master/kernel/kfifo.c

3 系统缓存优化-消除伪共享

缓存系统中是以缓存行(cache line) 为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。

当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

核心:Sequence

可看成是个AtomicLong,用于标识进度。还有防止不同Sequence之间CPU缓存伪共享(Flase Sharing)。


如下设计保证保存的 value 永远在一个缓存行中。(8 个long,正好 64 字节),空间换时间。这些变量就是没有实际意义,只是帮助我们进行缓存行填充(Padding Cache Line),使得我们能够尽可能地用上CPU高速缓存(CPU Cache)

image.png

若访问内置在CPU的L1 Cache或L2 Cache,访问延时是内存的1/15乃至1/100。而内存访问速度远慢于CPU。想追求极限性能,需尽可能多从CPU Cache拿数据,而非从内存。


CPU Cache装载内存里的数据,不是一个个字段加载,而是加载整个缓存行。

如定义长度64的long类型数组,则数据从内存加载到CPU Cache,不是一个个数组元素加载,而是一次性加载固定长度的一个缓存行。


64位Intel CPU计算机的缓存行通常64个字节(Bytes)。一个long数据需8字节,所以一下会加载8个long数据。

即一次加载数组里面连续的8个数值。这样的加载使得遍历数组元素时,会很快。因为后面连续7次的数据访问都会命中缓存,无需重新从内存里读取数据。


但不使用数组,而使用单独变量时,这就出问题了。

Disruptor RingBuffer(环形缓冲区)定义了RingBufferFields类,里面有indexMask和其他几个变量存放RingBuffer的内部状态信息。

11.png

CPU在加载数据时,自然也会把这个数据从内存加载到高速缓存。

但这时,高速缓存除了这个数据,还会加载这个数据前后定义的其他变量。


这时,问题就来了,Disruptor是个多线程的服务器框架,在这个数据前后定义的其他变量,可能会被多个不同线程更新、读取数据。这些写入及读取的请求,会来自不同 CPU Core。于是,为保证数据的同步更新,不得不把CPU Cache里的数据,重新写回内存或重新从内存里加载数据。


这些CPU Cache的写回和加载,都不是以一个变量作为单位。这些都是以整个Cache Line作为单位。

所以,当INITIAL_CURSOR_VALUE 前后的那些变量被写回到内存时,这个字段自己也写回到了内存,这个常量的缓存也就失效了。

当要再次读取这个值时,要再重新从内存读取。这就意味着,读取速度大大变慢。

image.png

image.png

image.png

对此,Disruptor利用了缓存行填充,在 RingBufferFields里面定义的变量的前后,分别定义了7个long类型的变量:


前面7个来自继承的 RingBufferPad 类

后面7个直接定义在 RingBuffer 类

这14个变量无任何实际用途。我们既不读他们,也不写他们。


而RingBufferFields里面定义的这些变量都是final,第一次写入后就不会再修改。

所以,一旦它被加载到CPU Cache后,只要被频繁读取访问,就不会再被换出Cache。这意味着,对于该值的读取速度,会一直是CPU Cache的访问速度,而非内存的访问速度。


目录
相关文章
|
存储 缓存 算法
并发编程系列教程(12) - Disruptor框架
并发编程系列教程(12) - Disruptor框架
109 0
|
5月前
|
监控 Java 测试技术
Java并发编程最佳实践:设计高性能的多线程系统
Java并发编程最佳实践:设计高性能的多线程系统
82 1
|
3月前
|
缓存 安全 算法
高性能无锁并发框架Disruptor,太强了!
高性能无锁并发框架Disruptor,太强了!
高性能无锁并发框架Disruptor,太强了!
|
4月前
|
监控 Java 开发者
【并发编程的终极简化】JDK 22结构化并发:让并发编程变得像写代码一样简单!
【9月更文挑战第8天】随着JDK 22的发布,结构化并发为Java编程带来了全新的并发编程体验。它不仅简化了并发编程的复杂性,提高了程序的可靠性和可观察性,还为开发者们提供了更加高效、简单的并发编程方式。我们相信,在未来的发展中,结构化并发将成为Java并发编程的主流方式之一,推动Java编程语言的进一步发展。让我们共同期待Java在并发编程领域的更多创新和突破!
|
6月前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
70 0
|
6月前
|
设计模式 存储 缓存
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
69 0
|
6月前
|
监控 Java 调度
使用Java实现高性能的定时任务调度
使用Java实现高性能的定时任务调度
|
7月前
|
存储 Java 调度
深入探索Java并发编程:ConcurrentSkipListSet的高效使用与实现原理
深入探索Java并发编程:ConcurrentSkipListSet的高效使用与实现原理
|
消息中间件 存储 缓存
并发编程之Disruptor框架介绍和高阶运用(一)
并发编程之Disruptor框架介绍和高阶运用
632 0
|
消息中间件 网络协议 Java
并发编程之Disruptor框架介绍和高阶运用(二)
并发编程之Disruptor框架介绍和高阶运用
711 0