Disruptor Ring Buffer as a Blocking Queue

Disruptor-cpp:C++高性能内存队列实现原理与实战应用 在并发编程领域,线程间高效、低延迟的数据交换是核心挑战。传统队列常因锁竞争、内存分配和缓存失效成为性能瓶颈。其原理在于通过预分配内存、序号驱动和避免伪共享等设计,将数据交换的延迟和吞吐量优化到极致。这项技术的价值在于为金融交易、实时风控、游戏服务器等对性能有严苛要求的场景提供了可预测的高性能解决方案。Disruptor-cpp作为该范式的C++实现,通过利用C++11原子操作和缓存行填充等技术,让开发者能够在C++生态中构建超低延迟的生产者-消费者系统,例如实现高吞吐的行情分发或订单处理引擎。 阅读详情
Author: Wang, Xinglang 

Abstract

For any concurrent multi-threaded system, distributed computing or otherwise,the inter-thread messaging component is an very important component. In Java, the JDK provided
ArrayBlockingQueue, LinkedBlockingQueue, TransferQueue. And Disruptor (http://lmaxexchange.github.io/disruptor/)
is very famous based on its high performance on its inter-thread messaging, but it does not expose as a BlockingQueue. This blog will introduce a new Blocking Queue based on its ring buffer and also with a benchmark result.

Why require Blocking Queue interface

Blocking queue interface is widely used by existed code, changing to Disruptor directly will cause big changes since disruptor want to control the whole thread scheduling. Second, Disruptor only call back when there is an event arrived, but it does not have a chance to let the application control the behavior when the queue is built-up and do some pro-active throttling.This blog will introduce a BlockingQueue implementation on top of RingBuffer, but there is a limitation,this queue can only be consumed by one consumer thread, but for producer, it can be single or multiple producer thread. This will be useful for the Actor Pattern, which use a blocking queue and one thread to drain queue. The reason is the offset of the consumer side can be hard to maintain if there are multiple consumer threads, multiple thread consumers should use Disruptor WorkerPool to replace the JDK Executor.

Implementation

The source code is available on
Github:https://github.com/xinglang/disruptorqueue/tree/master/disruptorqueue
Since this queue only supports one consumer, so let's call it SingleConsumerDisruptorQueue
The SingleConsumerDisruptorQueue will have a ring buffer and a sequence (consumedSeq) for the
cosnumer, the cosnumedSeq will be the gating sequence of the ring buffer. And there a knownPublishedSeq which used to remember the last known published sequence. Since it will be a
blocking queue, so the wait strategy will be BlockingWaitStrategy (Default one).

private final RingBuffer<Event<T>> ringBuffer;

private final Sequence consumedSeq;

private final SequenceBarrier barrier;

private long knownPublishedSeq;

public SingleConsumerDisruptorQueue(int bufferSize, boolean singleProducer) {

if (singleProducer) {

ringBuffer = RingBuffer.createSingleProducer(new Factory<T>(),

normalizeBufferSize(bufferSize));

} else {

ringBuffer = RingBuffer.createMultiProducer(new Factory<T>(),

normalizeBufferSize(bufferSize));

}

consumedSeq = new Sequence();

ringBuffer.addGatingSequences(consumedSeq);

barrier = ringBuffer.newBarrier();

long cursor = ringBuffer.getCursor();

consumedSeq.set(cursor);

knownPublishedSeq = cursor;

}

For the publish, just use ring buffer publish. And inside the ring buffer, there is a event holder which
acts as a value holder of the item.

@Override
public boolean offer(T e) {
long seq;
try {
seq = ringBuffer.tryNext();
} catch (InsufficientCapacityException e1) {
return false;
}
publish(e, seq);
return true;
}
private void publish(T e, long seq) {
Event<T> holder = ringBuffer.get(seq);
holder.setValue(e);
ringBuffer.publish(seq);
}

For the consume, there is a optimization since only one consumer thread. Each time when call the waitFor, it can get the last known published sequence, if the consumer sequence less than the last known published sequence, it does not need call the barrier waitFor method.

@Override

public T take() throws InterruptedException {
long l = consumedSeq.get() + 1;
while (knownPublishedSeq < l) {
try {
knownPublishedSeq = barrier.waitFor(l);
} catch (AlertException e) {
throw new IllegalStateException(e);
} catch (TimeoutException e) {
throw new IllegalStateException(e);
}
}
Event<T> eventHolder = ringBuffer.get(l);
consumedSeq.incrementAndGet();
return eventHolder.getValue();
}

Performace analysis

First of all, it can get all benefits from the ring buffer design:

  • Avoid false sharing
  • Pre-allocated ring buffer, no any instance created during publish/consume
  • Less context switch, the consumer can get a batch of events without interrupted

Below is a benchmark for the queue and LinkedBlockingQueue, ArrayBlockingQueue and Transfer Queue. The Benchmark run on a baremetal machine with Ubuntu, the benchmark use 1 consumer thread, and 1 to 4 producer thread, each round run 32M put/take, the object for put is a constant string, so there is no any GC overhead for the object creation.

Single Producer benchmark

 

$ perf stat java -jar disruptortest.jar type=dbq                          
Producers :1, buffer size: 262144, batch:0                                
SingleConsumerDisruptorQueue transfer rate : 19890 per ms, Used 1687ms for 33554432                                                                  
Performance counter stats for 'java -jar disruptortest.jar type=dbq':     
3729.421847 task-clock # 1.998 CPUs utilized   
1,891 context-switches # 0.001 M/sec           
                      76 CPU-migrations # 0.000 M/sec                            
9,357 page-faults # 0.003 M/sec      
9,434,280,791 cycles # 2.530 GHz [83.38%]  
5,489,619,603 stalled-cycles-frontend # 58.19% frontend cycles idle [83.35%] 
2,618,037,087 stalled-cycles-backend # 27.75% backend cycles idle [66.99%] 
10,797,968,145 instructions # 1.14 insns per cycle       
                                      # 0.51 stalled cycles per insn [83.55%]
1,742,973,721 branches # 467.358 M/sec [83.28%]
      10,213,770 branch-misses # 0.59% of all branches [83.12%]
1.866803438 seconds time elapsed   
            
$ perf stat java -jar disruptortest.jar type=abq                                 
Producers :1, buffer size: 262144, batch:0                                      
ArrayBlockingQueue transfer rate : 2694 per ms, Used 12451ms for 33554432    
Performance counter stats for 'java -jar disruptortest.jar type=abq':
22976.952946 task-clock # 1.824 CPUs utilized  
232,766 context-switches # 0.010 M/sec           
80 CPU-migrations # 0.000 M/sec    
68,531 page-faults # 0.003 M/sec     
58,643,663,103 cycles # 2.552 GHz [83.14%] 
51,767,105,241 stalled-cycles-frontend # 88.27% frontend cycles idle [83.32%]
47,084,355,024 stalled-cycles-backend # 80.29% backend cycles idle [66.51%]
   12,035,035,540 instructions # 0.21 insns per cycle        
                                        # 4.30 stalled cycles per insn [83.44%]
 2,016,738,256 branches # 87.772 M/sec [83.56%]
        20,147,764 branch-misses # 1.00% of all branches [83.49%]
12.596555382 seconds time elapsed                                         
$ perf stat java -jar disruptortest.jar type=lbq                                  
Producers :1, buffer size: 262144, batch:0                                        
LinkedBlockingQueue transfer rate : 1132 per ms, Used 29632ms for 33554432          
Performance counter stats for 'java -jar disruptortest.jar type=lbq':             
58707.942294 task-clock # 1.968 CPUs utilized 
82,377 context-switches # 0.001 M/sec         
97 CPU-migrations # 0.000 M/sec   
133,543 page-faults # 0.002 M/sec     
151,825,969,348 cycles # 2.586 GHz [83.27%] 
139,833,905,165 stalled-cycles-frontend # 92.10% frontend cycles idle [83.40%]
131,712,244,095 stalled-cycles-backend # 86.75% backend cycles idle [66.67%]
10,997,843,405 instructions # 0.07 insns per cycle    
                                          # 12.71 stalled cycles per insn [83.26%]
  1,701,879,665 branches # 28.989 M/sec [83.31%]
         23,369,660 branch-misses # 1.37% of all branches [83.35%]
29.830928757 seconds time elapsed                                            
$ perf stat java -jar disruptortest.jar type=tq                                      
Producers :1, buffer size: 262144, batch:0                                       
LinkedTransferQueue transfer rate : 2139 per ms, Used 15685ms for 33554432       
Performance counter stats for 'java -jar disruptortest.jar type=tq':             
107428.492713 task-clock # 6.737 CPUs utilized
10,542 context-switches # 0.000 M/sec         
100 CPU-migrations # 0.000 M/sec    
245,909 page-faults # 0.002 M/sec     
278,182,169,187 cycles # 2.589 GHz [83.33%] 
204,478,913,414 stalled-cycles-frontend # 73.51% frontend cycles idle [83.36%]
164,497,727,638 stalled-cycles-backend # 59.13% backend cycles idle [66.73%]
90,952,113,104 instructions # 0.33 insns per cycle    
                                         # 2.25 stalled cycles per insn [83.37%]
  32,522,385,525 branches # 302.735 M/sec [83.30%]
             57,227,684 branch-misses # 0.18% of all branches [83.28%]
15.947024802 seconds time elapsed                                                      

Multiple Producer benchmark

$ perf stat java -jar disruptortest.jar type=dq producer=4                        
Producers :4, buffer size: 262144, batch:0                                      
SingleConsumerDisruptorQueue transfer rate : 2859 per ms, Used 46941m for                                           134217728                                                                        
Performance counter stats for 'java -jar disruptortest.jar type=dq producer=4':   
                 118905.839793 task-clock # 2.523 CPUs utilized                          
2,172,912 context-switches # 0.018 M/sec            
280 CPU-migrations # 0.000 M/sec    
28,697 page-faults # 0.000 M/sec    
 ​141,597,737,150 cycles # 1.191 GHz [83.18%]  
113,618,387,640 stalled-cycles-frontend # 80.24% frontend cycles idle [83.42%]
  96,562,209,060 stalled-cycles-backend # 68.19% backend cycles idle [66.86%] 
55,227,379,587 instructions # 0.39 insns per cycle    
                                         # 2.06 stalled cycles per insn [83.45%]
  9,312,400,407 branches # 78.317 M/sec [83.19%]
         64,375,263 branch-misses # 0.69% of all branches [83.35%]
47.133747893 seconds time elapsed                                          
$ perf stat java -jar disruptortest.jar type=abq producer=4                   
Producers :4, buffer size: 262144, batch:0                                
ArrayBlockingQueue transfer rate : 2047 per ms, Used 65546ms for 134217728
Performance counter stats for 'java -jar disruptortest.jar type=abq producer=4':
Multiple Producer benchmark79345.046656 task-clock # 1.208 CPUs utilized                 
3,003,905 context-switches # 0.038 M/sec             
 594 CPU-migrations # 0.000 M/sec      
77,227 page-faults # 0.001 M/sec     
102,931,605,765 cycles # 1.297 GHz [83.10%]  
78,913,722,891 stalled-cycles-frontend # 76.67% frontend cycles idle [83.46%]
65,701,179,927 stalled-cycles-backend # 63.83% backend cycles idle [66.99%]
52,891,419,177 instructions # 0.51 insns per cycle     
                                        # 1.49 stalled cycles per insn [83.41%]
  9,307,141,741 branches # 117.300 M/sec [83.21%]
        79,855,221 branch-misses # 0.86% of all branches [83.23%]
65.694123910 seconds time elapsed                                            
$ perf stat java -jar disruptortest.jar type=lbq producer=4                     
Producers :4, buffer size: 262144, batch:0                                  
LinkedBlockingQueue transfer rate : 2795 per ms, Used 48014ms for 134217728     
Performance counter stats for 'java -jar disruptortest.jar type=lbq producer=4':
110080.375452 task-clock # 2.284 CPUs utilized  
3,644,802 context-switches # 0.033 M/sec            
597 CPU-migrations # 0.000 M/sec    
136,440 page-faults # 0.001 M/sec     
185,250,018,068 cycles # 1.683 GHz [83.46%] 
144,448,559,949 stalled-cycles-frontend # 77.97% frontend cycles idle [83.62%]
118,250,468,418 stalled-cycles-backend # 63.83% backend cycles idle [66.28%]
73,113,563,433 instructions # 0.39 insns per cycle    
                                         # 1.98 stalled cycles per insn [83.21%]
  12,028,209,235 branches # 109.268 M/sec [83.25%]
        129,234,077 branch-misses # 1.07% of all branches [83.40%]
48.189813503 seconds time elapsed                                        
$ perf stat java -jar disruptortest.jar type=tq producer=4                 
Producers :4, buffer size: 262144, batch:0                                 
LinkedTransferQueue transfer rate : 1438 per ms, Used 93273ms for 134217728
Performance counter stats for 'java -jar disruptortest.jar type=tq producer=4':
761878.416668 task-clock # 8.122 CPUs utilized
71,371 context-switches # 0.000 M/sec       
203 CPU-migrations # 0.000 M/sec  
670,788 page-faults # 0.001 M/sec   
1,976,200,012,808 cycles # 2.594 GHz [83.33%] 
1,584,264,715,610 stalled-cycles-frontend # 80.17% frontend cycles idle [83.34%]
1,368,861,011,899 stalled-cycles-backend # 69.27% backend cycles idle [66.68%]
487,816,405,509 instructions # 0.25 insns per cycle   
                                           # 3.25 stalled cycles per insn [83.34%]
   169,135,278,863 branches # 221.998 M/sec [83.33%]
          615,658,238 branch-misses # 0.36% of all branches [83.33%]
93.798977802 seconds time elapsed                                                        

Conclusion

Using RingBuffer of disruptor to create a blocking queue is possible. For single producer/consumer case, it can be 5x faster than JDK default blocking queue implementation. In multiple producer case, it is much faster than arrayblocking queue and transfer queue, the linked blocking queue can achieve similar throughput but disruptor one has less context switches and less memory footprint. The only limitation is it only support the single consumer thread. The benefits for the BlockingQueue implementation on top of RingBuffer is it can be just a replacement for the existed code, and it give user more control via the BlockingQueue interface, the WorkerPool provided by disruptor only allow user to give a event handler for callback.

Disruptor源码介绍(一)-RingBuffer RingBufferDisruptor最重要的核心组件,可以理解为一个环形队列,用来存储事件,生产者往队列上面存放事件,消费者去读取。内部是如何实现的,我们来看下源码。1.类结构/** * Ring based store of reusable entries containing the data representing * an event being exchanged betwe... 阅读详情

相关推荐

DisruptorBlockingQueue压力测试性能对比

DisruptorBlockingQueue压力测试性能对比 欢迎关注作者博客 简书传送门 1、先熟悉下什么是阻塞队列! 传送门 2、代码压测 2.1、公共部分 package com.bfxy.disruptor.ability; public interface Constants { int EVENT_NUM_OHM = 100000000; int EVENT_NUM_F...

阿祥小王子的博客 3110

《重学Java高并发》Disruptor使用实战

上文已经详细介绍了disruptor,也体会了并发编程的奥妙,接下来将理论结合实战,本文和大家分享一下disruptor的使用,加深对disruptor工具包对理解。 1、 disruptor常用类一览 disruptor的常用类体系如下图所示: 其职责说明如下: RingBuffer 环形队列,disruptor中的核心存储类 Sequencer 序号实现器,维护发送者发送的序号生成逻辑、消费方获取可消费的序号,是无锁化访问的核心实现类,共有两个实现类,MultiProducerSequence

中间件兴趣圈 2948

你应该知道的高性能无锁队列Disruptor

1.何为队列 听到队列相信大家对其并不陌生,在我们现实生活中队列随处可见,去超市结账,你会看见大家都会一排排的站得好好的,等待结账,为什么要站得一排排的,你想象一下大家都没有素质,一窝蜂的上去结账,不仅让这个超市崩溃,还会容易造成各种踩踏事件,当然这些事其实在我们现实中也是会经常发生。 当然在计算机世界中,队列是属于一种数据结构,队列采用的FIFO(first in firstout),新元素(等...

weixin_34319640的博客 398

系统级性能分析工具 — Perf

从2.6.31内核开始,linux内核自带了一个性能分析工具perf,能够进行函数级与指令级的热点查找。 perf Performance analysis tools for Linux. Performance counters for Linux are a new kernel-based subsystem that provide a framework fo...

weixin_30619101的博客 4273

perf stat 输出解读

perf stat 输出解读 原文链接:http://zhengheng.me/2015/11/12/perf-stat/   task-clock:用于执行程序的CPU时间,单位是ms(毫秒)。第二列中的CPU utillized则是指这个进程在运行perf的这段时间内的CPU利用率,该数值是由task-clock除以最后一行的time elapsed(也就是wall time,真...

飘过的春风 2万+

互联网技术16——Disruptor

学之前 在看这篇博客之前,我想说的是,如果是准备入门Disruptor,建议掌握一些重要方法和特性,至于要实现哪种功能,建议掌握大致流程,以后的使用过程中慢慢去消化,如果学习任务比较多,又期望通过自己的入门式学习而全部掌握Disruptor并熟练运用时间成本有些划不来的。所以我准备有时间做一个简单归纳,这篇博客介绍了几种使用案例,同时推荐一个Disruptor的学习网站 http://ifev...

qq_28240551的博客 1109

性能极限挑战:用LMAX Disruptor为Kovenant异步队列加速

Kovenant 是 Kotlin 生态中轻量级的 Promise 异步编程库,而它内置的 kovenant-disruptor 模块能把底层异步队列替换为金融级高性能环形缓冲——LMAX Disruptor,让任务投递与回调调度真正逼近无锁极限。本文为你完整讲解如何为 Kovenant 异步队列接入 Disruptor,并带来可复现的性能测试方法与配置技巧。 ## Kovenant 的异步队列

gitblog_00453的博客 995

disruptor高性能环形队列

简介 说到Disruptor,首先需要谈谈LMAX。它是欧洲第一家也是唯一一家采用多边交易设施Multilateral Trading Facility(MTF)拥有交易所牌照和经纪商牌照的欧洲顶级金融公司。它们所构建的金融交易平台,建立在JVM平台上, 能够以很低的延迟(latency)...

choulu6980的博客 1143

Disruptor源码解析三 RingBuffer解析

前言 前面篇章介绍了下Sequence相关类,这里主要介绍下集成了Sequence类的disruptor主要结构RingBuffer。 主要内容 类的继承结构: 类的主要成员: RingBuffer的要点 避免缓存行伪共享 RingBufferFields中entries数组, 前后都加了缓存行填充避免伪共享。 预初始化 entries预先都用工厂类进行了构造 集成了Sequencer 使用的是SingleProducerSequencer 或者MultiProducerSeque.

destiny4Y的专栏 986

linux性能分析工具_系统级性能分析工具 — Perf

嵌入式linux QQ交流群:175159209,欢迎爱好者加入交流技术问题!从2.6.31内核开始,linux内核自带了一个性能分析工具perf,能够进行函数级与指令级的热点查找。perfPerformance analysis tools for Linux.Performance counters for Linux are a new kernel-based subsystem...

weixin_39867893的博客 412

ArrayBlockingQueue, LinkedBlockingQueue, ConcurrentLinkedQueue, RingBuffer

1. ArrayBlockingQueue, LinkedBlockingQueue, ConcurrentLinkedQueue ArrayBlockingQueue, LinkedBlockingQueue 继承自 BlockingQueue, 他们的特点就是 Blocking, Blocking 特有的方法就是 take() 和 put(), 这两个方法是阻塞方法, 每当队列容量满的时候,...

weixin_34419326的博客 231

Ring Buffer (circular Buffer)环形缓冲区简介

关于环形缓冲区的知识,请看这里 http://en.wikipedia.org/wiki/Circular_buffer  上面这个网址已经介绍得非常详细了。 下面这个网址有 RingBuffer的C代码实现, 其实是一个C的开源库   liblcthw 里实现的。 http://c.learncodethehardway.org/book/ex44.html

Langeldep的专栏 3万+

Disruptor PK BlockingQueue

  package com.disruptor.test3; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import org.junit.Test; ...

yekui的专栏 361

CPU Utilization is Wrong

The metric we all use for CPU utilization is deeply misleading, and getting worse every year. What is CPU utilization? How busy your processors are? No, that's not what it measures. Yes, I'm talking a

u013597671的专栏 846

开发内功修炼CPU篇

最近网络在爆炒一篇标题为《互联网不需要中年人》,疯狂渲染35岁的码农的前程问题,制造焦虑。本来我觉得这个事情应该只是媒体博眼球的一个炒作而已。不过恰恰最近面试了有70多人,其中有很多工作7,8年以上的的同学。这些人里基本上可以非常明确地划分成两类。第一类是虽然工作了7,8年以上了,但是所有的经验都集中在业务层。换句话说,并不是有7-8年经验,而是工作了7-8年而已。稍微深入问一点性能相关的问题都没...

~~ LINUX ~~ 1627

构建高性能服务(三)Java高性能缓冲设计 vs Disruptor vs LinkedBlockingQueue

一个仅仅部署在4台服务器上的服务,每秒向Database写入数据超过100万行数据,每分钟产生超过1G的数据。而每台服务器(8核12G)上CPU占用不到100%,load不超过5。这是怎么做到呢?下面将给你描述这个架构,它的核心是一个高效缓冲区设计,我们对它的要求是: 1,该缓存区要尽量简单 2,尽量避免生产者线程和消费者线程锁 3,尽量避免大量GC 缓冲 vs 性能瓶颈 提高硬盘写入I...

移动互联网后端技术 946

Muduo网络库源码分析(二) 定时器TimeQueue,Timer,TimerId

首先,我们先要明白为什么需要设计这样一个定时器类? 在开发Linux网络程序时,通常需要维护多个定时器,如维护客户端心跳时间、检查多个数据包的超时重传等。如果采用linux的SIGALARM信号实现,则会带来较大的系统开销,且不便于管理。 Muduo 的 TimerQueue 采用了最简单的实现(链表)来管理定时器,它的效率比不上常见的 binary heap 的做法,如果程序

andylau00j的专栏 491

perf学习-linux自带性能分析工具

目前在做性能分析的事情,之前没怎么接触perf,找了几篇文章梳理了一下,按照问题的形式记录在这里。 方便自己查看。   什么是perf? linux性能调优工具,32内核以上自带的工具,软件性能分析。在2.6.31及后续版本的Linux内核里,安装perf非常的容易。 几乎能够处理所有与性能相关的事件。   什么是性能事件? 指在处理器或者操作系统中发生,可能影响到程序性能的硬件...

iamzhongyong的专栏 1100
上一篇: 改进你的网页
下一篇: 在开发测试中使用HBaseMiniCluster
ebay
博客等级 码龄24年 109粉丝 69原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值