Rx TaskPoolScheduler vs EventLoopScheduler,内存使用情况

时间:2014-10-02 11:36:53

标签: c#-4.0 system.reactive reactive-programming

我正在尝试对众多独立数据源进行POC。经典观察者风格应用的排序。数据馈送的数量可能从几百到几千不等,观察者的数量可能会有所不同,从2到20000.以下是简单数据馈送可观察模型的快速示例:

    public class FeedMockUp
    {
        private readonly IScheduler observerScheduler;
        private readonly Random rnd = new Random((int)DateTime.Now.Ticks);

        private readonly Subject<double> sourceObservable;
        private readonly IObservable<double> testedObservable;

        public FeedMockUp(IScheduler observerScheduler)
        {
            this.observerScheduler = observerScheduler;
            sourceObservable = new Subject<double>();

            testedObservable =
                Observable.Create<double>(x =>
                {
                    var underlyingSourceDisposable =
                        sourceObservable
                            .Subscribe(_ => x.OnNext(rnd.NextDouble()));
                    return underlyingSourceDisposable;
                });
        }

        public IDisposable SubscribeToUnderlyingFeed(int numberOfSubscribers)
        {
            int counter = 0;
            var disposable = new CompositeDisposable();

            for (int i = 0; i < numberOfSubscribers; i++)
            {
                disposable.Add(testedObservable
                    .ObserveOn(observerScheduler)
                    .Subscribe(_ => Interlocked.Increment(ref counter)));
            }

            return disposable;
        }

        public void PushNewFeed()
        {
            sourceObservable.OnNext(rnd.NextDouble());
        }
    }    

虽然我正在玩shedulers以提高observables更新的吞吐量,但我注意到使用EventLoopScheduler内存消耗的应用程序有100个数据源和1000个观察者是非常不变的,1000名观察者它是~100Mb并且在向混合添加新观察者时线性增长。

然而,当我尝试使用TaskPoolScheduler时,在x86进程中我开始获得OutOfMemoryException个异常并且x64进程内存消耗爆炸,或者更确切地说,变得非常不确定,范围从1Gb到2Gb,仅500个观察者随着新观察员的加入,这种情况几乎成倍增长。

这是我用于测试的代码。你能看出它有什么问题吗?为什么性能如此差异?猜猜,这里有一些内部复制/排队,但这只是我的猜测。理想情况下,我想知道这里发生了什么,而不是潜入RX代码库......

    private static void Main(string[] args)
    {
        const int displayItemCount = 100;
        const int callbackCount = 500;

        //var rtScheduler = new EventLoopScheduler(); 
        var rtScheduler = TaskPoolScheduler.Default;
        var rtFeeds = new List<FeedMockUp>();
        for (int i = 0; i < displayItemCount; i++)
        {
            var mockFeed = new FeedMockUp(rtScheduler);
            mockFeed.SubscribeToUnderlyingFeed(callbackCount);
            rtFeeds.Add(mockFeed);
        }
        foreach (var rtFeedMockUp in rtFeeds)
        {
            rtFeedMockUp.PushNewFeed();
        }
        Console.WriteLine("Memory used for feed {0} mockups with {1} observers / callbacks. Memory {2} Mb",
            displayItemCount, callbackCount, Environment.WorkingSet / (1024 * 1024));
Console.ReadKey();

}

2 个答案:

答案 0 :(得分:13)

ObserveOnTaskPoolScheduler一起使用,实际上是为每个观察者创建一个LongRunning任务。

默认TaskScheduler结束creating a Thread for each LongRunning tasks

每个线程的堆栈大约保留1MB。

因此,使用TaskPoolScheduler的500名观察者将保留至少500MB。你可以看到它的发展方向......

另一方面,EventLoopScheduler在单个线程上运行。因此,有效地将ObserveOn与此调度程序一起使用只会在调度程序的工作队列中添加一个条目。此条目比Thread的1MB成本要小得多。

因此,EventLoopScheduler对于这种情况来说内存效率要高得多,但是它也会连续地通知观察者,如果有很多观察者并且来源产生的频率很高,那么你将开始累积未发送事件的缓冲区。

TaskPoolScheduler内存效率较低,但会同时通知观察者,因此可以通过利用计算机上的所有核心来处理比EventLoopScheduler更高频率的事件。

答案 1 :(得分:6)

您可能想要使用TaskPoolScheduler.Default.DisableOptimizations(typeof(ISchedulerLongRunning))。如果您不介意失去并行性,EventLoopScheduler是一个不错的选择。

如果您仍希望并行执行工作但想要使用线程池线程,则此选项更可取。