Я пытаюсь перечислить большой IEnumerable
один раз и наблюдайте за перечислением с различными присоединенными операторами (Count
, Sum
, Average
и т. д.). Очевидный способ — преобразовать его в IObservable
с помощью метода ToObservable
, а затем подпишите на него наблюдателя. Я заметил, что это намного медленнее, чем другие методы, такие как выполнение простого цикла и уведомление наблюдателя о каждой итерации или использование метода Observable.Create
вместо ToObservable
. Разница существенная: в 20-30 раз медленнее. Так оно и есть, или я что-то не так делаю?
using System;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reactive.Threading.Tasks;
public static class Program
{
static void Main(string[] args)
{
const int COUNT = 10_000_000;
Method1(COUNT);
Method2(COUNT);
Method3(COUNT);
}
static void Method1(int count)
{
var source = Enumerable.Range(0, count);
var subject = new Subject<int>();
var stopwatch = Stopwatch.StartNew();
source.ToObservable().Subscribe(subject);
Console.WriteLine($"ToObservable: {stopwatch.ElapsedMilliseconds:#,0} msec");
}
static void Method2(int count)
{
var source = Enumerable.Range(0, count);
var subject = new Subject<int>();
var stopwatch = Stopwatch.StartNew();
foreach (var item in source) subject.OnNext(item);
subject.OnCompleted();
Console.WriteLine($"Loop & Notify: {stopwatch.ElapsedMilliseconds:#,0} msec");
}
static void Method3(int count)
{
var source = Enumerable.Range(0, count);
var subject = new Subject<int>();
var stopwatch = Stopwatch.StartNew();
Observable.Create<int>(o =>
{
foreach (var item in source) o.OnNext(item);
o.OnCompleted();
return Disposable.Empty;
}).Subscribe(subject);
Console.WriteLine($"Observable.Create: {stopwatch.ElapsedMilliseconds:#,0} msec");
}
}
Выход:
ToObservable: 7,576 msec
Loop & Notify: 273 msec
Observable.Create: 511 msec
.NET Core 3.0, C# 8, System.Reactive 4.3.2, Windows 10, консольное приложение, встроенная версия
Обновление: вот пример фактической функциональности, которую я хочу реализовать:
var source = Enumerable.Range(0, 10_000_000).Select(i => (long)i);
var subject = new Subject<long>();
var cntTask = subject.Count().ToTask();
var sumTask = subject.Sum().ToTask();
var avgTask = subject.Average().ToTask();
source.ToObservable().Subscribe(subject);
Console.WriteLine($"Count: {cntTask.Result:#,0}, Sum: {sumTask.Result:#,0}, Average: {avgTask.Result:#,0.0}");
Выход:
Количество: 10 000 000, Сумма: 49 999 995 000 000, Среднее значение: 4 999 999,5
Важное отличие этого подхода от использования стандартного LINQ заключается в том, что перечисляемый источник перечисляется только один раз.
Еще одно наблюдение: использование ToObservable(Scheduler.Immediate)
немного быстрее (около 20%), чем ToObservable()
.
Stopwatch
. - person Theodor Zoulias   schedule 02.04.2020Method3(COUNT); Method2(COUNT); Method1(COUNT);
. Я получил аналогичные результаты. - person Theodor Zoulias   schedule 02.04.2020var source = Enumerable.Range(0, count);
) - person Fildor   schedule 02.04.2020ToObservable
ровно в 24,8 или 25,2 раза медленнее. Это не имеет никакого значения для моего варианта использования. В обоих случаях я склонен не использовать его, а вместо этого использовать один из других методов. - person Theodor Zoulias   schedule 02.04.2020.ToObservable()
по причинам, которые я изложил в своем ответе. Скорость здесь не цель. - person Enigmativity   schedule 02.04.2020Observable.Create
, особенно если вы в конечном итоге делаетеreturn Disposable.Empty;
. - person Enigmativity   schedule 02.04.2020