StackOverflow 有人说自己的Disruptor.NET 代码比 BlockingCollection 还有慢 2 倍,并且把完整代码贴出,楼下几个老外也的回复说了一堆,但是没研究出个所以然来,讨论到最后甚至说可能你的场景不适合 Disruptor,我对此表示怀疑,BlockingCollection 内部实现比较简单粗暴,必要时就加锁,取数据时用信号量等待添加操作完成,而 Disruptor 却不是一般的实现,是专门针对 CPU 缓存的特性优化过的,内部没有锁只有 CAS 原子操作,而且还考虑到了 false sharing,因此理论上 Disruptor 不会比 BlockingCollection 慢。

可是既然实际应用上出现问题,那就要分析下原因了。

把他的代码弄下来看了一下,问题多多啊。

在 Disruptor EventHandler 里面不定时调用 Console.WriteLine ,但是在 BlockingCollection 的 Handler 里面却只是记录了数据, Console.WriteLine 内部可是有锁的,调用的开销很大,如何能取得公平的结果呢?

另外 RingBuffer 的 Size 太小只有 64,严重影响 Disruptor 的表现,实际测试对比下来,应该 1024 或更大。

还有 BlockingCollection 里面的 while (!dataItems.IsCompleted) 写的也有问题,即使 BlockingCollection Producer 在循环中一直做添加操作,BlockingCollection 内部状态也并不是一直在添加状态中,这样导致添加循环还没做完,可是计时器的循环已经提前结束,导致 BlockingCollection 测得时间少于实际实际。

Task.Factory.StartNew(() => {
while (!dataItems.IsCompleted)
{ ValueEntry ve = null;
try
{
ve = dataItems.Take();
long microseconds = sw[ve.Value].ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
results[ve.Value] = microseconds; //Console.WriteLine("elapsed microseconds = " + microseconds);
//Console.WriteLine("Event handled: Value = {0} (processed event {1}", ve.Value, ve.Value);
}
catch (InvalidOperationException) { }
}
}, TaskCreationOptions.LongRunning); for (int i = ; i < length; i++)
{
var valueToSet = i; ValueEntry entry = new ValueEntry();
entry.Value = valueToSet; sw[i].Restart();
dataItems.Add(entry); //Console.WriteLine("Published entry {0}, value {1}", valueToSet, entry.Value);
//Thread.Sleep(1000);
}

然后重新修改了他的代码,实测 Disruptor 10 倍速度于 BlockingCollection (这里插一句题外话,Disruptor .NET 版本的速度全面快于 Java 版本,不少场景下的速度比 Java 版本要快 10 倍,.NET 版是从 Java 移植过来的实现也和 Java 保持一直,是哪些语言特性导致性能差异这么大呢?)。

Disruptor is 10x faster than BlockingCollection with multi producer (10 parallel producet), 2x faster than BlockingCollection with Single producer:

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Disruptor;
using Disruptor.Dsl;
using NUnit.Framework; namespace DisruptorTest.Ds
{
public sealed class ValueEntry
{
internal int Id { get; set; }
} class MyHandler : IEventHandler<ValueEntry>
{
public void OnEvent(ValueEntry data, long sequence, bool endOfBatch)
{
}
} [TestFixture]
public class DisruptorPerformanceTest
{
private volatile bool collectionAddEnded; private int producerCount = ;
private int runCount = ;
private int RingBufferAndCapacitySize = ; [TestCase()]
public async Task TestBoth()
{
for (int i = ; i < ; i++)
{
foreach (var rs in new int[] {, , , /*,4096,4096*2*/})
{
Console.WriteLine($"RingBufferAndCapacitySize:{rs}, producerCount:{producerCount}, runCount:{runCount} of {i}");
RingBufferAndCapacitySize = rs;
await DisruptorTest();
await BlockingCollectionTest();
}
}
} [TestCase()]
public async Task BlockingCollectionTest()
{
var sw = new Stopwatch();
BlockingCollection<ValueEntry> dataItems = new BlockingCollection<ValueEntry>(RingBufferAndCapacitySize); sw.Start(); collectionAddEnded = false; // A simple blocking consumer with no cancellation.
var task = Task.Factory.StartNew(() =>
{
while (!collectionAddEnded && !dataItems.IsCompleted)
{
//if (!dataItems.IsCompleted && dataItems.TryTake(out var ve))
if (dataItems.TryTake(out var ve))
{
}
}
}, TaskCreationOptions.LongRunning); var tasks = new Task[producerCount];
for (int t = ; t < producerCount; t++)
{
tasks[t] = Task.Run(() =>
{
for (int i = ; i < runCount; i++)
{
ValueEntry entry = new ValueEntry();
entry.Id = i;
dataItems.Add(entry);
}
});
} await Task.WhenAll(tasks); collectionAddEnded = true;
await task; sw.Stop(); Console.WriteLine($"BlockingCollectionTest Time:{sw.ElapsedMilliseconds/1000d}");
} [TestCase()]
public async Task DisruptorTest()
{
var disruptor =
new Disruptor.Dsl.Disruptor<ValueEntry>(() => new ValueEntry(), RingBufferAndCapacitySize, TaskScheduler.Default,
producerCount > ? ProducerType.Multi : ProducerType.Single, new BlockingWaitStrategy());
disruptor.HandleEventsWith(new MyHandler()); var _ringBuffer = disruptor.Start(); Stopwatch sw = Stopwatch.StartNew(); sw.Start(); var tasks = new Task[producerCount];
for (int t = ; t < producerCount; t++)
{
tasks[t] = Task.Run(() =>
{
for (int i = ; i < runCount; i++)
{
long sequenceNo = _ringBuffer.Next();
_ringBuffer[sequenceNo].Id = ;
_ringBuffer.Publish(sequenceNo);
}
});
} await Task.WhenAll(tasks); disruptor.Shutdown(); sw.Stop();
Console.WriteLine($"DisruptorTest Time:{sw.ElapsedMilliseconds/1000d}s");
}
}
}
 

BlockingCollectionTest with a shared ValueEntry instance (no new ValueEntry() in for loop)

  • RingBufferAndCapacitySize:64, producerCount:10, runCount:1000000 of 0

    DisruptorTest Time:16.962s

    BlockingCollectionTest Time:18.399

  • RingBufferAndCapacitySize:512, producerCount:10, runCount:1000000 of 0 DisruptorTest Time:6.101s

    BlockingCollectionTest Time:19.526

  • RingBufferAndCapacitySize:1024, producerCount:10, runCount:1000000 of 0

    DisruptorTest Time:2.928s

    BlockingCollectionTest Time:20.25

  • RingBufferAndCapacitySize:2048, producerCount:10, runCount:1000000 of 0

    DisruptorTest Time:2.448s

    BlockingCollectionTest Time:20.649

BlockingCollectionTest create a new ValueEntry() in for loop

  • RingBufferAndCapacitySize:64, producerCount:10, runCount:1000000 of 0

    DisruptorTest Time:27.374s

    BlockingCollectionTest Time:21.955

  • RingBufferAndCapacitySize:512, producerCount:10, runCount:1000000 of 0

    DisruptorTest Time:5.011s

    BlockingCollectionTest Time:20.127

  • RingBufferAndCapacitySize:1024, producerCount:10, runCount:1000000 of 0

    DisruptorTest Time:2.877s

    BlockingCollectionTest Time:22.656

  • RingBufferAndCapacitySize:2048, producerCount:10, runCount:1000000 of 0

    DisruptorTest Time:2.384s

    BlockingCollectionTest Time:23.567

基于 CAS 无锁实现的 Disruptor.NET 居然慢于 BlockingCollection,是真的吗?的更多相关文章

  1. (转载)java高并发:CAS无锁原理及广泛应用

    java高并发:CAS无锁原理及广泛应用   版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 ...

  2. java并发:AtomicInteger 以及CAS无锁算法【转载】

    1 AtomicInteger解析 众所周知,在多线程并发的情况下,对于成员变量,可能是线程不安全的: 一个很简单的例子,假设我存在两个线程,让一个整数自增1000次,那么最终的值应该是1000:但是 ...

  3. CAS无锁机制原理

    原子类 java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读 ...

  4. CAS无锁算法与ConcurrentLinkedQueue

    CAS:Compare and Swap 比较并交换 java.util.concurrent包完全建立在CAS之上的,没有CAS就没有并发包.并发包借助了CAS无锁算法实现了区别于synchroni ...

  5. CAS无锁技术

    前言:关于同步,很多人都知道synchronized,Reentrantlock等加锁技术,这种方式也很好理解,是在线程访问的临界区资源上建立一个阻塞机制,需要线程等待 其它线程释放了锁,它才能运行. ...

  6. CAS无锁实现原理以及ABA问题

    CAS(比较与交换,Compare and swap) 是一种有名的无锁算法.无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(N ...

  7. 探索CAS无锁技术

    前言:关于同步,很多人都知道synchronized,Reentrantlock等加锁技术,这种方式也很好理解,是在线程访问的临界区资源上建立一个阻塞机制,需要线程等待 其它线程释放了锁,它才能运行. ...

  8. CAS无锁模式

    一.java内存模型:JMM 在内存模型当中定义一个主内存,所有声明的实例变量都存在于主内存当中,主内存的数据会共享给所有线程,每一个线程有一个块工作内存,工作内存当中主内存数据的副本当更新数据时,会 ...

  9. 无锁并发框架Disruptor学习入门

    刚刚听说disruptor,大概理一下,只为方便自己理解,文末是一些自己认为比较好的博文,如果有需要的同学可以参考. 本文目标:快速了解Disruptor是什么,主要概念,怎么用 1.Disrupto ...

随机推荐

  1. font-family与font-face的区别

    font-family:指定字体 设置后,电脑上无该字体时,观看网页不能显示该字体效果, 针对中文版操作系统,为保证网页效果,通常只指定:宋体.黑体.微软雅黑等系统上默认自带的字体. font-fac ...

  2. 我学习Python的经历

    1. 被逼无奈找到了Python Python作为当今非常热的话题,常常被各级别的程序员所提到,也有很多的小白问我,到底啥是Python,它能干啥! 我用Python时间不是特别长,最早使用是在201 ...

  3. VMware Fushion解决:与vmmon模块的版本不匹配: 需要385.0,现有330.0。

    可以按下列步骤解决: 1. 退出VMware fusion2. 打开[终端]3. 执行命令:sudo rm -rf /System/Library/Extensions/vmmon.kext ,根据提 ...

  4. NodeJS4-2静态资源服务器实战_实现获取文件路径

    实例2 : 实现获取文件路径,判断是文件还是文件夹,如果是文件夹就显示里面的列表文件,如果是文件就显示里面的内容 defaultConfig.js module.exports={ root:proc ...

  5. SpringBoot微服务电商项目开发实战 --- Redis缓存雪崩、缓存穿透、缓存击穿防范

    最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...

  6. [ASP.NET Core 3框架揭秘] 文件系统[3]:物理文件系统

    ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件.View文件以及作为Web资源的静态文件.物理文件系统由定义在NuGet包"Microsoft.Extension ...

  7. VNC连接CentOS7远程桌面

    1.在centos7安装图形化 先安装图形用户接口X Window System,再安装GNOME桌面. [root@centos7 ~]# yum groupinstall -y "X W ...

  8. Kubernetes行业调研报告:多集群、多云部署成企业首选策略

    新兴的多集群.多云部署成为首选的企业策略,而边缘部署则呈上升趋势 2019年11月5日,业界采用最广泛的Kubernetes管理平台创造者Rancher Labs(以下简称Rancher)发布了首份调 ...

  9. 设置tomcat为自动启动

    第一步:设置tomcat为服务启动项 进入dos窗口,输入service.bat install,启动服务, 这里要注意的是,如果直接在cmd输入报错,需要你进入到tomcat的目录下执行cmd 第二 ...

  10. python 调试大法

    说在前面 我觉得没有什么错误是调试器无法解决的,如果没有,那我再说一遍,如果有,那当我没说 一.抛出异常 可以通过 raise 语句抛出异常,使程序在我们已经知道的缺陷处停下,并进入到 except ...