Disruptor应该比BlockingCollection快得多。
在我之前的问题中, Why is my disruptor example so slow? 我写了两个测试。 Disruptor
花了大约1微秒(或更少),而BlockingCollection花了大约14微秒。
所以我决定在我的程序中使用Disruptor
,但是当我实现它时,我发现现在Disruptor
花费50
微秒,而BlockingCollection仍然花费14-18
微秒。
我已将我的生产代码修改为“独立测试”,Disruptor
仍然花费50微秒。为什么呢?
简化测试如下。在这个测试中,我有两个选择。第一个选项是Sleep for 1 ms
。然后Disruptor
花费30-50微秒来交付。第二个选项是模拟活动。然后Disruptor
花费7微秒来交付。与BlockingCollection
相同的测试结果为14-18微秒。那么为什么Disruptor不比BlockingCollection快?
在我的真实应用程序Disruptor
花费50微秒来提供太多的东西!我希望它能以比1微秒更快的速度传递信息。
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Disruptor;
namespace DisruptorTest
{
public sealed class ValueEntry
{
internal int Id { get; set; }
}
class DisruptorTest
{
public class MyHandler : IEventHandler<ValueEntry>
{
private DisruptorTest _parent;
public MyHandler(DisruptorTest parent)
{
this._parent = parent;
}
public void OnNext(ValueEntry data, long sequence, bool endOfBatch)
{
_parent.sw.Stop();
long microseconds = _parent.sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
// Filter out abnormal delays > 1000
if (microseconds < 1000)
{
_parent.sum += (int)microseconds;
_parent.count++;
if (_parent.count % 1000 == 0)
{
Console.WriteLine("average disruptor delay (microseconds) = {0}", _parent.sum / _parent.count);
}
}
}
}
private RingBuffer<ValueEntry> _ringBuffer;
private const int RingSize = 64;
static void Main(string[] args)
{
new DisruptorTest().Run();
}
public void Run()
{
var disruptor = new Disruptor.Dsl.Disruptor<ValueEntry>(() => new ValueEntry(), RingSize, TaskScheduler.Default);
disruptor.HandleEventsWith(new MyHandler(this));
_ringBuffer = disruptor.Start();
for (int i = 0; i < 10001; i++)
{
Do();
// We need to simulate activity to allow event to deliver
// Option1. just Sleep. Result 30-50 microseconds.
Thread.Sleep(1);
// Option2. Do something. Result ~7 microseconds.
//factorial = 1;
//for (int j = 1; j < 100000; j++)
//{
// factorial *= j;
//}
}
}
public static int factorial;
private Stopwatch sw = Stopwatch.StartNew();
private int sum;
private int count;
public void Do()
{
long sequenceNo = _ringBuffer.Next();
_ringBuffer[sequenceNo].Id = 0;
sw.Restart();
_ringBuffer.Publish(sequenceNo);
}
}
}
旧代码。现在应该被忽略:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Disruptor;
namespace DisruptorTest
{
public sealed class ValueEntry
{
internal int Id { get; set; }
}
class DisruptorTest
{
public class MyHandler : IEventHandler<ValueEntry>
{
private readonly int _ordinal;
private readonly int _consumers;
private DisruptorTest _parent;
public MyHandler(int ordinal, int consumers, DisruptorTest parent)
{
_ordinal = ordinal;
_consumers = consumers;
this._parent = parent;
}
public void OnNext(ValueEntry data, long sequence, bool endOfBatch)
{
if ((sequence % _consumers) == _ordinal)
{
var id = data.Id;
_parent.sw[id].Stop();
long microseconds = _parent.sw[id].ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
// filter out abnormal delays > 1000
if (microseconds < 1000)
{
_parent.sum[id] += (int)microseconds;
_parent.count[id]++;
if (_parent.count[id] % 10 == 0)
{
Console.WriteLine("Id = {0} average disruptor delay (microseconds) = {1}",
id, _parent.sum[id] / _parent.count[id]);
}
}
}
}
}
private const int NumberOfThreads = 1;
private RingBuffer<ValueEntry> _ringBuffer;
private const int RingSize = 64;
static void Main(string[] args)
{
new DisruptorTest().Run();
}
public void Run()
{
var disruptor = new Disruptor.Dsl.Disruptor<ValueEntry>(() => new ValueEntry(), RingSize, TaskScheduler.Default);
for (int i = 0; i < NumberOfThreads; i++)
disruptor.HandleEventsWith(new MyHandler(i, NumberOfThreads, this));
for (int i = 0; i < sw.Length; i++)
{
sw[i] = Stopwatch.StartNew();
}
_ringBuffer = disruptor.Start();
//var rnd = new Random();
for (int i = 0; i < 1000; i++)
{
//Do(rnd.Next(MaxId));
Do(i % MaxId);
Thread.Sleep(1);
}
}
private const int MaxId = 100;
private Stopwatch[] sw = new Stopwatch[MaxId];
private int[] sum = new int[MaxId];
private int[] count = new int[MaxId];
public void Do(int id)
{
long sequenceNo = _ringBuffer.Next();
_ringBuffer[sequenceNo].Id = id;
sw[id].Restart();
_ringBuffer.Publish(sequenceNo);
}
}
}
输出:
......
Id = 91 average disruptor delay (microseconds) = 50
Id = 92 average disruptor delay (microseconds) = 48
Id = 93 average disruptor delay (microseconds) = 35
Id = 94 average disruptor delay (microseconds) = 35
Id = 95 average disruptor delay (microseconds) = 51
Id = 96 average disruptor delay (microseconds) = 55
Id = 97 average disruptor delay (microseconds) = 38
Id = 98 average disruptor delay (microseconds) = 37
Id = 99 average disruptor delay (microseconds) = 45
答案 0 :(得分:4)
您仍然在做同样的事情:您正在测量发布单个项目所需的时间。
public void Do(int id)
{
long sequenceNo = _ringBuffer.Next();
_ringBuffer[sequenceNo].Id = id;
sw[id].Restart(); // <--- You're doing this EVERY TIME YOU PUBLISH an item!
_ringBuffer.Publish(sequenceNo);
}
在上一个问题中,您被告知您应该测量数百个出版物,以便正确使用Stopwatch
精度。
此外,您还在测试过程中写入控制台。避免这样做:
if (_parent.count[id] % 10 == 0)
{
Console.WriteLine("Id = {0} average disruptor delay (microseconds) = {1}",
id, _parent.sum[id] / _parent.count[id]);
}
至少,你应该尝试清理你的代码;我重新组织了一下,所以它不是那么混乱:http://pastie.org/5382971
Disrputors开始并不那么简单,现在我们必须处理您的代码并尝试告诉您如何解决它。更重要的是:当你有意大利面条代码时,你无法进行性能优化或测试。尽量保持一切简单和干净。在这个阶段,您的代码既不简单也不干净。
让我们从您的私有成员变量的可怕命名约定开始:
private const int NumberOfThreads = 1;
private RingBuffer<ValueEntry> _ringBuffer;
private const int RingSize = 64;
private const int MaxId = 100
private Stopwatch[] sw = new Stopwatch[MaxId];
private int[] sum = new int[MaxId];
private int[] count = new int[MaxId];
保持一致:
private const int _numberOfThreads = 1;
private RingBuffer<ValueEntry> _ringBuffer;
private const int _ringSize = 64;
private const int _maxId = 100
private Stopwatch[] _sw = new Stopwatch[MaxId];
private int[] _sum = new int[MaxId];
private int[] _count = new int[MaxId];
其他一些指示:
Martin和Michael告诉你的第一件事就是性能测试也必须非常好,所以他们花了很多时间构建testing framework。
CountDownEvent
。所以让我们先解决第一个争议:the precision of the stopwatch should indeed be sufficient.
Int64 frequency = Stopwatch.Frequency;
Console.WriteLine( " Timer frequency in ticks per second = {0}", frequency );
Int64 nanosecPerTick = (1000L * 1000L * 1000L) / frequency;
Console.WriteLine( " Timer is accurate within {0} nanoseconds", nanosecPerTick );
在我的机器上,分辨率在320纳秒之内。所以OP是正确的,定时器的分辨率应该不是问题。
我知道OP希望衡量一个项目的平均交付量,但至少有两种方法可以做到。
我们必须调查差异。在概念层面上,您正在执行以下代码:
在代码中:
Stopwatch sw = new Stopwatch();
long totalMicroseconds = 0;
int numItems = 1000;
for(int i = 0; i < numItems; i++)
{
sw.Reset();
sw.Start();
OneItemDelivery();
sw.Stop();
totalMicroseconds += sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
}
long avgOneItemDelivery = totalMicroseconds/numItems;
测量性能的另一种方法是:
在代码中:
sw.Start();
for(int i = 0; i < numItems; i++)
{
OneItemDelivery();
}
sw.Stop();
totalMicroseconds = sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
long avgOneItemDelivery = totalMicroseconds/numItems;
每个人都有自己的问题:
您已经注意到Sleep
语句会降低性能,因此我建议您进行简单的计算。计算阶乘似乎是一个好主意,只需要进行一个非常小的计算:不需要100000,100也应该没问题。
当然,你不需要等待2分钟进行测试,但10-20秒不应该是一个问题。
答案 1 :(得分:0)
我读了您从Why is my disruptor example so slow?编写的BlockingCollecton代码,您在Disruptor中添加了许多Console.WriteLine
,但在BlockingCollection中却没有一个,Console.WriteLine
很慢,里面有一个锁。
您的RingBufferSize
太小,此效果会影响性能,应为1024或更大。
和while (!dataItems.IsCompleted)
可能有问题,BlockCollection始终处于添加状态,这将导致线程提早结束。
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 = 0; 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快10倍,比具有单一生产者的BlockingCollection快2倍:
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 = 10;
private int runCount = 1000000;
private int RingBufferAndCapacitySize = 1024;
[TestCase()]
public async Task TestBoth()
{
for (int i = 0; i < 1; i++)
{
foreach (var rs in new int[] {64, 512, 1024, 2048 /*,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 = 0; t < producerCount; t++)
{
tasks[t] = Task.Run(() =>
{
for (int i = 0; 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 > 1 ? 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 = 0; t < producerCount; t++)
{
tasks[t] = Task.Run(() =>
{
for (int i = 0; i < runCount; i++)
{
long sequenceNo = _ringBuffer.Next();
_ringBuffer[sequenceNo].Id = 0;
_ringBuffer.Publish(sequenceNo);
}
});
}
await Task.WhenAll(tasks);
disruptor.Shutdown();
sw.Stop();
Console.WriteLine($"DisruptorTest Time:{sw.ElapsedMilliseconds/1000d}s");
}
}
}
RingBufferAndCapacitySize:64,producerCount:10,runCount:1000000 of 0
DisruptorTest时间:16.962s
BlockingCollectionTest时间:18.399
RingBufferAndCapacitySize:512,producerCount:10,runCount:1000000为0 DisruptorTest时间:6.101s
BlockingCollectionTest时间:19.526
RingBufferAndCapacitySize:1024,producerCount:10,runCount:1000000 of 0
DisruptorTest时间:2.928秒
BlockingCollectionTest时间:20.25
RingBufferAndCapacitySize:2048,producerCount:10,runCount:1000000 of 0
DisruptorTest时间:2.448秒
BlockingCollectionTest时间:20.649
RingBufferAndCapacitySize:64,producerCount:10,runCount:1000000 of 0
DisruptorTest时间:27.374s
BlockingCollectionTest时间:21.955
RingBufferAndCapacitySize:512,producerCount:10,runCount:1000000 of 0
DisruptorTest时间:5.011秒
BlockingCollectionTest时间:20.127
RingBufferAndCapacitySize:1024,producerCount:10,runCount:1000000 of 0
DisruptorTest时间:2.877秒
BlockingCollectionTest时间:22.656
RingBufferAndCapacitySize:2048,producerCount:10,runCount:1000000 of 0
DisruptorTest时间:2.384秒
BlockingCollectionTest时间:23.567