Rx v1.0 の実装を見ているようです。主題の実装は、Rx v2.0 で完全にオーバーホールされ、呼び出しパスでの割り当てと重いロックが回避されました。同様に、Rx クエリ パイプラインは、同じ基準を念頭に置いて改訂されました。
Rx v2.0 のパフォーマンス向上の詳細については、http://blogs.msdn.com/b/rxteam/archive/2012/03/12/reactive-extensions-v2-0-beta-available-now.aspxを参照してください。(この記事はベータ版のものですが、ほとんどの情報は RTM ビルドに適用されます。いくつかの点は改善されています。)
具体的には、サブジェクトについては、2 つ以上のオブザーバーが接続されている場合、通常、マルチキャスト デリゲートよりもパフォーマンスが優れています。オブザーバーがアタッチされていない場合、オブザーバーでの仮想メソッド呼び出しのコストは、イベントに使用される null チェックと呼び出しパターンを上回ります。1 つのオブザーバーを使用すると、呼び出しリスト (~ マルチキャスト部分のないデリゲート) を通過することを回避できますが、仮想呼び出しのコストは依然として表示されます。より多くのオブザーバーがアタッチされているため、仮想メソッドの foreach ループは、マルチキャスト デリゲートの背後にある機構よりも高速になる傾向があります。
以下は、Rx テストから直接ベンチマーク コードを抜粋したものです (一部のパラメーターを定数にし、内部型への参照を削除しています)。
var e = default(Action<int>);
var a = new Action<int>(_ => { });
var s = new Subject<int>();
var n = new NopObserver<int>();
var N = 20;
var M = 10000000;
var sw = new Stopwatch();
for (int i = 0; i < N; i++)
{
sw.Restart();
for (int j = 0; j < M; j++)
{
var f = e;
if (f != null)
f(42);
}
sw.Stop();
var t = sw.Elapsed;
Console.WriteLine("E({0}) = {1}", i, t);
sw.Restart();
for (int j = 0; j < M; j++)
{
s.OnNext(42);
}
sw.Stop();
var u = sw.Elapsed;
Console.WriteLine("O({0}) = {1}", i, u);
var d = u.TotalMilliseconds / t.TotalMilliseconds;
Console.ForegroundColor = d <= 1 ? ConsoleColor.Green : ConsoleColor.Red;
Console.WriteLine(d + " - " + GC.CollectionCount(0));
Console.ResetColor();
Console.WriteLine();
e += a;
s.Subscribe(n);
}
このマシンでは、最初の 2 つの反復が赤くなります。その後の反復 (ハンドラー数 >= 2) では、20% から 35% のスピードアップが見られます。ただし、これらのようなベンチマークに関する通常の警告はすべて適用されます :-)。
また、Rx のパイプラインが長くなるにつれて、(安全性を保証するための) オブザーバー ラッピングのオーバーヘッドが減少することに注意してください。これは、Rx v2.0 が信頼できるオペレーター間で内部ハンドシェイクを実行し、余分なラッピングを回避するためです。最終的なユーザー サブスクリプションのみが、Rx とユーザー提供のオブザーバー コードの間の仮想呼び出しの別のレイヤーの対象となり、適切な例外の伝播などを保証します。Rx v1.0 では、各オペレーターにセーフティ ネットが提供され、追加各オペレータを通過するメッセージごとに 2 ~ 4 の追加の仮想コール。
要するに、何らかのテストを行うことにした場合は、Rx v2.0 を使用してください。パフォーマンスは、このリリースの最大の機能でした :-)。