私は大規模な列挙しようとしています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演算子を使用する場合と比較したこのアプローチの重要な違いは、列挙可能なソースが1回だけ列挙されることです。
もう1つの観察:を使用する方ToObservable(Scheduler.Immediate)
がよりもわずかに高速(約20%)ですToObservable()
。