3

標準の .NET イベントの代わりに、IObservable、Subject、および Observer (後者の 2 つは Reactive 拡張機能にあります) を使用するように、一部のインターフェイスを作り直すことを検討しています。実装を調べたところ、サブジェクトは、コールバックする必要がある IObserver のリストをロックし、新しい配列を作成し、IObserver 参照をその新しい配列にコピーしてから呼び出すことがわかりました。

MulticastDelegate の実装を見ると、MulticastDelegate.GetInvocationList の実装も新しい配列を作成し、呼び出されるデリゲートをその配列にコピーしてから呼び出すことがわかります。明確でないのは、マルチキャスト デリゲートを呼び出すときに GetInvocationList が呼び出されるか、メモリを割り当てない方法でフレームワーク内で処理されるかです。マルチキャスト デリゲートを呼び出すと、新しい配列が割り当てられますか? または、イベントが発生したときに新しい配列が割り当てられないように、フレームワークが処理しますか?

私たちのアプリケーションはメモリの割り当てとレイテンシに非常に敏感であるため、新しいインターフェイスに移行することで、イベントの呼び出しでより多くのメモリを割り当てないようにしています。内部でもいくつかのテストを実行します。

4

4 に答える 4

3

明確でないのは、マルチキャスト デリゲートを呼び出すときに GetInvocationList が呼び出されるか、メモリを割り当てない方法でフレームワーク内で処理されるかです。

デリゲートを呼び出しても、 は呼び出されませんGetInvocationList。これは、デリゲートの実行に使用される実際のコードではなく、デリゲートを操作および検査するためのコードです。ランタイム自体は実際には呼び出しを内部で実行しますが、これは実際には IL 内にないためです。これは、8.9.3 の CLI 仕様に記載されています。

ほとんどの場合、デリゲートは単なる別の種類のユーザー定義クラスのように見えますが、厳密に制御されています。メソッドの実装は、ユーザーコードではなく、VES* によって提供されます。

基本的に、実際の呼び出しはランタイムによって内部的に処理されます。

 * VES == CLI 仕様の「仮想実行システム」。これは、ランタイム自体を実行するコードに使用される用語です。

于 2012-09-24T20:50:50.373 に答える
1

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 を使用してください。パフォーマンスは、このリリースの最大の機能でした :-)。

于 2012-10-05T09:29:24.133 に答える
0

Rxと割り当てに関して; Rx v2.0には、実行される割り当てとロックの数を減らすために多大な労力が費やされました。V1は優れた製品ですが、APIの公開面で2年分の業界フィードバックを得る場所であることが証明されています。これにより業界が何を必要としているかが明らかになると、Rxチームは立ち去り、内部作業に取り組みました。私が見たところ、Bart DeSmetは、Rxを使用した64wayシステムで90〜95%を超えるCPU使用率を得ることができました。これは、CPUバウンドであり、コンテキストスイッチング、ロック、IOバウンドではないことを示しています(ただし、私は一生の間、それを示す投稿を見つけることができません)。つまり、システムは、他の配管を処理しないRxクエリを処理するために完全に使用されていました。

Rxを使用した私の経験から、あなたを捕まえることができる他の多くのものがあります、割り当ては通常それらの1つではありません。低レイテンシーシステムの要件があり、割り当てによってGCジッターが発生する可能性があることは理解していますが、シングルスレッドパイプラインを構築することになると思います。そうでなければ、マルチスレッドアプリのコンテキストスイッチングは、あちこちでGCのコストをはるかに上回ると思います。このシングルスレッドパイプラインの場合、得られる主な利点は、イベントを処理するよりもはるかに読みやすいクエリを構築するためのRx演算子を作成できることです。

最後に、コードでサブジェクトやObserverクラスを使用しないようにします。これは、設計上の欠陥を示しています。それらを使い始めた場合は、ここ(または公式Rxフォーラムhttp://social.msdn.microsoft.com/Forums/en-US/rx/threads)で問題スペースを共有することをお勧めします。コミュニティはより良いガイダンスを提供できます。

于 2012-10-03T10:21:25.477 に答える
0

以下のテスト アプリをいじってみると、MulticastDelegate を呼び出してもマネージド メモリが割り当てられないことがわかりました。違うことを知っている人がいたら教えてください。


using System;
using System.Diagnostics;

internal class Program
{
    private static event Action A;

    private static void Method1() {}
    private static void Method2() {}
    private static void Method3() {}

    private static void Main()
    {
        A += Method1;
        A += Method2;
        A += Method3;

        var totalMemory = GC.GetTotalMemory(true);

        while(true)
        {
            A();

            // Uncommenting the line below will cause the Debug.Assert to be hit.
            // var a = new int[] {};

            if (totalMemory != GC.GetTotalMemory(false))
            {
                // Does not get hit unless line above allocating an array is
                // uncommented.
                Debug.Assert(false);
            }
        }
    }
}
于 2012-09-24T21:26:40.957 に答える