简体   繁体   English

反应性扩展似乎很慢-我做错什么了吗?

[英]Reactive Extensions seem very slow - am I doing something wrong?

I'm evaluating Rx for a trading platform project that will need to process thousands of messages a second. 我正在评估一个交易平台项目的Rx,该项目需要每秒处理数千条消息。 The existing platform has a complex event routing system (multicast delegates) that responds to these messages and performs a lot of subsequent processing. 现有平台具有复杂的事件路由系统(多播委托),该系统响应这些消息并执行大量后续处理。

I've looked at Reactive Extensions for the obvious benefits but noticed it's somewhat slower, usual 100 times slower. 我已经看过了Reactive Extensions的明显好处,但注意到它的速度要慢一些,通常要慢100倍。

I've created unit test to demonstrate this which runs a simple increment 1 million times, using various Rx flavours and a straight-out-the-box delegate "control" test. 我已经创建了单元测试来演示此操作,它使用各种Rx风格和直接的委托“控件”测试运行了100万次简单增量。

Here are the results: 结果如下:

Delegate                                 - (1000000) - 00:00:00.0410000
Observable.Range()                       - (1000000) - 00:00:04.8760000
Subject.Subscribe() - NewThread          - (1000000) - 00:00:02.7630000
Subject.Subscribe() - CurrentThread      - (1000000) - 00:00:03.0280000
Subject.Subscribe() - Immediate          - (1000000) - 00:00:03.0030000
Subject.Subscribe() - ThreadPool         - (1000000) - 00:00:02.9800000
Subject.Subscribe() - Dispatcher         - (1000000) - 00:00:03.0360000

As you can see, all the Rx methods are ~100 times slower than a delegate equivalent. 如您所见,所有Rx方法的速度都比委托方法慢100倍。 Obviously Rx is doing a lot under the covers that will be of use in a more complex example, but this just seems incredibly slow. 显然,Rx在一个更复杂的示例中会做很多事情,但是这似乎非常慢。

Is this normal or are my testing assumptions invalid? 这是正常现象还是我的测试假设无效? Nunit code for the above below - 上面的Nunit代码如下-

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Concurrency;

namespace RxTests
{
    [TestFixture]
    class ReactiveExtensionsBenchmark_Tests
    {
        private int counter = 0;

        [Test]
        public void ReactiveExtensionsPerformanceComparisons()
        {
            int iterations = 1000000;

            Action<int> a = (i) => { counter++; };

            DelegateSmokeTest(iterations, a);
            ObservableRangeTest(iterations, a);
            SubjectSubscribeTest(iterations, a, Scheduler.NewThread, "NewThread");
            SubjectSubscribeTest(iterations, a, Scheduler.CurrentThread, "CurrentThread");
            SubjectSubscribeTest(iterations, a, Scheduler.Immediate, "Immediate");
            SubjectSubscribeTest(iterations, a, Scheduler.ThreadPool, "ThreadPool");
            SubjectSubscribeTest(iterations, a, Scheduler.Dispatcher, "Dispatcher");
        }

        public void ObservableRangeTest(int iterations, Action<int> action)
        {
            counter = 0;

            long start = DateTime.Now.Ticks;

            Observable.Range(0, iterations).Subscribe(action);

            OutputTestDuration("Observable.Range()", start);
        }


        public void SubjectSubscribeTest(int iterations, Action<int> action, IScheduler scheduler, string mode)
        {
            counter = 0;

            var eventSubject = new Subject<int>();
            var events = eventSubject.SubscribeOn(scheduler); //edited - thanks dtb
            events.Subscribe(action);

            long start = DateTime.Now.Ticks;

            Enumerable.Range(0, iterations).ToList().ForEach
                (
                    a => eventSubject.OnNext(1)
                );

            OutputTestDuration("Subject.Subscribe() - " + mode, start);
        }

        public void DelegateSmokeTest(int iterations, Action<int> action)
        {
            counter = 0;
            long start = DateTime.Now.Ticks;

            Enumerable.Range(0, iterations).ToList().ForEach
                (
                    a => action(1)
                );

            OutputTestDuration("Delegate", start);
        }


        /// <summary>
        /// Output helper
        /// </summary>
        /// <param name="test"></param>
        /// <param name="duration"></param>
        public void OutputTestDuration(string test, long duration)
        {
            Debug.WriteLine(string.Format("{0, -40} - ({1}) - {2}", test, counter, ElapsedDuration(duration)));
        }

        /// <summary>
        /// Test timing helper
        /// </summary>
        /// <param name="elapsedTicks"></param>
        /// <returns></returns>
        public string ElapsedDuration(long elapsedTicks)
        {
            return new TimeSpan(DateTime.Now.Ticks - elapsedTicks).ToString();
        }

    }
}

My guess is that the Rx team focuses on building the functionality first and doesn't care about performance optimization yet. 我的猜测是,Rx团队首先关注于构建功能,而不在乎性能优化。

Use a profiler to determine bottlenecks and replace slow Rx classes with your own optimized versions. 使用探查器确定瓶颈,并用自己的优化版本替换慢速Rx类。

Below are two examples. 以下是两个示例。

Results: 结果:

Delegate                                 - (1000000) - 00:00:00.0368748

Simple - NewThread                       - (1000000) - 00:00:00.0207676
Simple - CurrentThread                   - (1000000) - 00:00:00.0214599
Simple - Immediate                       - (1000000) - 00:00:00.0162026
Simple - ThreadPool                      - (1000000) - 00:00:00.0169848

FastSubject.Subscribe() - NewThread      - (1000000) - 00:00:00.0588149
FastSubject.Subscribe() - CurrentThread  - (1000000) - 00:00:00.0508842
FastSubject.Subscribe() - Immediate      - (1000000) - 00:00:00.0513911
FastSubject.Subscribe() - ThreadPool     - (1000000) - 00:00:00.0529137

First of all, it seems to matter a lot how the observable is implemented. 首先,如何实现可观察性似乎很重要。 Here's an observable that cannot be unsubscribed from, but it's fast: 这是一个不可取消的观察对象,但它很快:

private IObservable<int> CreateFastObservable(int iterations)
{
    return Observable.Create<int>(observer =>
    {
        new Thread(_ =>
        {
            for (int i = 0; i < iterations; i++)
            {
                observer.OnNext(i);
            }
            observer.OnCompleted();
        }).Start();
        return () => { };
    });
}

Test: 测试:

public void SimpleObserveTest(int iterations, Action<int> action, IScheduler scheduler, string mode)
{
    counter = 0;

    var start = Stopwatch.StartNew();

    var observable = CreateFastObservable(iterations);

    observable.SubscribeOn(scheduler).Run(action);

    OutputTestDuration("Simple - " + mode, start);
}

Subjects add a lot of overhead. 主题会增加很多开销。 Here's a subject that is stripped of much of the functionality expected from a subject, but it's fast: 这是一个主题,去除了该主题预期的许多功能,但速度很快:

class FastSubject<T> : ISubject<T>
{
    private event Action onCompleted;
    private event Action<Exception> onError;
    private event Action<T> onNext;

    public FastSubject()
    {
        onCompleted += () => { };
        onError += error => { };
        onNext += value => { };
    }

    public void OnCompleted()
    {
        this.onCompleted();
    }

    public void OnError(Exception error)
    {
        this.onError(error);
    }

    public void OnNext(T value)
    {
        this.onNext(value);
    }

    public IDisposable Subscribe(IObserver<T> observer)
    {
        this.onCompleted += observer.OnCompleted;
        this.onError += observer.OnError;
        this.onNext += observer.OnNext;

        return Disposable.Create(() =>
        {
            this.onCompleted -= observer.OnCompleted;
            this.onError -= observer.OnError;
            this.onNext -= observer.OnNext;
        });
    }
}

Test: 测试:

public void FastSubjectSubscribeTest(int iterations, Action<int> action, IScheduler scheduler, string mode)
{
    counter = 0;

    var start = Stopwatch.StartNew();

    var observable = new ConnectableObservable<int>(CreateFastObservable(iterations), new FastSubject<int>()).RefCount();

    observable.SubscribeOn(scheduler).Run(action);

    OutputTestDuration("FastSubject.Subscribe() - " + mode, start);
}

Update for Rx 2.0: I took the code from the original post with (almost) the latest Linqpad beta 4.42.04 (well there's a 06, but anyway): Rx 2.0的更新:我从原始帖子中获取了(几乎)最新的Linqpad beta 4.42.04中的代码(当然还有06,但无论如何): Rx主组件

... and adjusted it slightly to use the new Rx v2 scheduler syntax: ...并稍作调整以使用新的Rx v2调度程序语法:

        public void ReactiveExtensionsPerformanceComparisons()
    {
        int iterations = 1000000;

        Action<int> a = (i) => { counter++; };

        DelegateSmokeTest(iterations, a);
        ObservableRangeTest(iterations, a);
        SubjectSubscribeTest(iterations, a, NewThreadScheduler.Default, "NewThread");
        SubjectSubscribeTest(iterations, a, CurrentThreadScheduler.Instance, "CurrentThread");
        SubjectSubscribeTest(iterations, a, ImmediateScheduler.Instance, "Immediate");
        SubjectSubscribeTest(iterations, a, ThreadPoolScheduler.Instance, "ThreadPool");
        // I *think* this is the same as the ThreadPool scheduler in my case
        SubjectSubscribeTest(iterations, a, DefaultScheduler.Instance, "Default");                
        // doesn't work, as LinqPad has no Dispatcher attched to the Gui thread, maybe there's a workaround; the Instance property on it is obsolete
        //SubjectSubscribeTest(iterations, a, DispatcherScheduler.Current, "ThreadPool");
    }

Note: results vary wildly, in rare cases Threadpool beats newThread, but in most cases NewThread has a slight edge above the schedulers below it in the list: 注意:结果差异很大,在极少数情况下,Threadpool胜过newThread,但在大多数情况下,NewThread在列表中位于其下方的调度程序上方略有优势:

Delegate                                 - (1000000) - 00:00:00.0440025
Observable.Range()                       - (1000000) - 00:00:01.9251101
Subject.Subscribe() - NewThread          - (1000000) - 00:00:00.0400023
Subject.Subscribe() - CurrentThread      - (1000000) - 00:00:00.0530030
Subject.Subscribe() - Immediate          - (1000000) - 00:00:00.0490028
Subject.Subscribe() - ThreadPool         - (1000000) - 00:00:00.0490028
Subject.Subscribe() - Default            - (1000000) - 00:00:00.0480028

So it seems they worked pretty hard on performance.. 因此,似乎他们在性能方面非常努力。

Remember that your Delegate doesn't guarantee any thread safety - it literally calls the delegate from whatever thread it's called from, whereas when you call Observable.ObserveOn to marshall notifications to other threads, Rx.NET has to do locking to make sure it does what you think it does. 记住,您的Delegate不能保证任何线程安全-它实际上是从被调用的任何线程中调用委托,而当您调用Observable.ObserveOn来编组对其他线程的通知时,Rx.NET必须进行锁定以确保它可以执行您认为它会做什么。

So, Delegates might move super fast, but if you want to build something practical using it, you're going to end up building synchronization by-hand which will slow you down. 因此,代表们的动作可能会很快,但是如果您想使用它来构建实用的东西,那么最终将需要手工建立同步,这会使您的速度变慢。 That being said, Rx, just like LINQ, is an abstraction - if you need it to be ridiculously fast, you have to start writing ugly code. 话虽如此,Rx就像LINQ一样,是一种抽象-如果您需要它快得离谱,就必须开始编写难看的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM