【问题标题】:My code with disruptor-net is slower than BlockingCollection我的中断网代码比 BlockingCollection 慢
【发布时间】:2012-11-15 14:39:28
【问题描述】:

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

【问题讨论】:

  • 请解释你认为你在测试中做了什么。很难跟踪你的测试。
  • 我只是将数字从 1 发布到 MaxId 到 NumberOfThreads 消费者。我确实为每个 id 测量了“平均一项延迟”(我为此使用了数组)。我认为测试可以简化,我会尝试。

标签: c# disruptor-pattern


【解决方案1】:

您仍在做同样的事情:您正在测量发布单个项目需要多少时间。

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

Disrputor 一开始并不那么简单,现在我们必须处理您的代码并尝试告诉您如何修复它。更重要的是:当你有意大利面条代码时,你不能进行性能优化或测试。尽量保持一切简单和干净。在这个阶段,你的代码既不简单也不干净。

让我们从私有成员变量的可怕命名约定开始:

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];

其他一些指针:

  • 摆脱嵌套类。
  • 将 main 移出一个单独的类(例如 Program)。

构建一个好的测试

Martin 和 Michael 告诉您的第一件事是性能测试也必须非常好,因此他们花费了大量时间来构建 testing framework

  • 我建议您尝试几百万个事件,而不是 1000 个事件。
  • 确保所有事件只使用一个计时器。
  • 开始处理项目时启动计时器,并在没有更多项目要处理时停止。
  • 知道您何时完成处理项目的一种有效方法是使用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 想要衡量一件商品的平均交付情况,但有(至少)两种方法可以做到这一点。

我们必须调查差异。在概念层面上,您所做的与下面的代码完全相同:

  1. 您正在运行大量迭代。
  2. 测量每一个。
  3. 您计算总数。
  4. 您在最后计算平均值。

在代码中:

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;

另一种衡量性能的方法是:

  1. 启动计时器。
  2. 运行所有迭代。
  3. 停止计时器。
  4. 计算平均时间。

在代码中:

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 秒应该不是问题。

【讨论】:

  • 我正在衡量“平均一件商品的交付”,因为这是我需要衡量的!秒表精度绰绰有余!在我的实际应用程序中,我确实传递了一条消息,但我没有传递数百万条消息!当Stopwatch 已经停止时,我正在写信给控制台,所以它不会改变任何东西。测试有点复杂,因为我刚刚从我的真实应用程序中学习并修改了它。所以这个测试非常接近我在现实生活中的测试。我想说这正是我需要优化的应用程序。
  • 将数字从 1000 更改为百万不会改变任何内容,但在发布的代码中我使用了 1000,因此人们无需在测试运行时等待 1-2 分钟。相同的测试,但使用BlockingCollection 快 2-3 倍。为什么在我的测试中 BlockingCollection 比 Disruptor 快?
  • 关于代码约定。我已经按照 Resharper 的建议命名了变量。所以const 应该是Uppercased
  • @javapowered 对我来说,您似乎真的不了解性能测试...您的测量方式基本上不合适,并且会给您带来不可靠/错误的结果...请在回答...
  • @Yahia 对我来说你不明白我不需要测量“测试”。我已经测量了实际应用。而且我发现使用Disruptor 实际应用程序更慢!我的测试是我真实应用程序的简化版本。这是完全有效的,我希望 Disruptor 比 BlockingCollection 更快。
【解决方案2】:

我读了你从Why is my disruptor example so slow?写的BlockingCollection代码,你在Disruptor中添加了很多Console.WriteLine,但在BlockingCollection中没有一个,Console.WriteLine很慢,里面有锁。

你的RingBufferSize太小了,这会影响性能,应该是1024或更大。

while (!dataItems.IsCompleted)可能有问题,BlockCollection一直处于adding状态,会导致线程提前结束。

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");
        }
    }
}

具有共享 ValueEntry 实例的 BlockingCollectionTest(for 循环中没有新的 ValueEntry())

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

    DisruptorTest 时间:16.962s

    BlockingCollectionTest 时间:18.399

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

    BlockingCollectionTest 时间:19.526

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

    DisruptorTest Time:2.928s

    BlockingCollectionTest 时间:20.25

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

    DisruptorTest Time:2.448s

    BlockingCollectionTest 时间:20.649

BlockingCollectionTest 在 for 循环中创建一个新的 ValueEntry()

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

    DisruptorTest 时间:27.374s

    BlockingCollectionTest 时间:21.955

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

    DisruptorTest 时间:5.011s

    BlockingCollectionTest 时间:20.127

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

    DisruptorTest 时间:2.877s

    BlockingCollectionTest 时间:22.656

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

    DisruptorTest 时间:2.384s

    BlockingCollectionTest 时间:23.567

https://www.cnblogs.com/darklx/p/11755686.html

【讨论】:

    猜你喜欢
    • 2019-10-05
    • 2018-08-16
    • 2013-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-26
    • 1970-01-01
    • 2021-03-10
    相关资源
    最近更新 更多