11

この質問の一部はデリゲートに関するものであり、一部はジェネリックに関するものです。

簡略化されたコードを考えると:

internal sealed class TypeDispatchProcessor
{
    private readonly Dictionary<Type, Delegate> _actionByType 
        = new Dictionary<Type, Delegate>();

    public void RegisterProcedure<T>(Action<T> action)
    {
        _actionByType[typeof(T)] = action;
    }

    public void ProcessItem(object item)
    {
        Delegate action;
        if (_actionByType.TryGetValue(item.GetType(), out action))
        {
            // Can this call to DynamicInvoke be avoided?
            action.DynamicInvoke(item);
        }
    }
}

SOの他の場所で、デリゲートを直接(括弧付きで)呼び出す方が、呼び出すよりも桁違いに速いことを読みましたDynamicInvoke。これは理にかなっています。

上記のコードサンプルでは、​​型チェックを実行して、どういうわけかパフォーマンスを向上させることができるかどうか疑問に思っています。

いくつかのコンテキスト:さまざまなハンドラーにファームアウトされるオブジェクトのストリームがあり、それらのハンドラーは実行時に登録/登録解除できます。上記のパターンは私の目的には完全に機能しますが、可能であればよりスッキリさせたいと思います。

1つのオプションは、に格納Action<object>し、デリゲートを別のデリゲートでDictionaryラップすることです。Action<T>この2回目の間接呼び出しが影響するパフォーマンスの変化はまだ比較していません。

4

3 に答える 3

24

呼び出しをラップする方が、を使用するよりもはるかに効率的だと強く思いますDynamicInvoke。その場合、コードは次のようになります。

internal sealed class TypeDispatchProcessor
{
    private readonly Dictionary<Type, Action<object>> _actionByType 
        = new Dictionary<Type, Action<object>>();

    public void RegisterProcedure<T>(Action<T> action)
    {
        _actionByType[typeof(T)] = item => action((T) item);
    }

    public void ProcessItem(object item)
    {
        Action<object> action;
        if (_actionByType.TryGetValue(item.GetType(), out action))
        {
            action(item);
        }
    }
}

ベンチマークする価値はありますが、これははるかに効率的だと思います。DynamicInvokeラップされたデリゲートでの単純なキャストではなく、リフレクションなどですべての引数をチェックする必要があります。

于 2009-07-12T13:54:12.760 に答える
7

そこで、これについていくつかの測定を行いました。

var delegates = new List<Delegate>();
var actions = new List<Action<object>>();

const int dataCount = 100;
const int loopCount = 10000;

for (int i = 0; i < dataCount; i++)
{
    Action<int> a = d => { };
    delegates.Add(a);
    actions.Add(o => a((int)o));
}

var sw = Stopwatch.StartNew();
for (int i = 0; i < loopCount; i++)
{
    foreach (var action in actions)
        action(i);
}
Console.Out.WriteLine("{0:#,##0} Action<object> calls in {1:#,##0.###} ms",
    loopCount * dataCount, sw.Elapsed.TotalMilliseconds);

sw = Stopwatch.StartNew();
for (int i = 0; i < loopCount; i++)
{
    foreach (var del in delegates)
        del.DynamicInvoke(i);
}
Console.Out.WriteLine("{0:#,##0} DynamicInvoke calls in {1:#,##0.###} ms",
    loopCount * dataCount, sw.Elapsed.TotalMilliseconds);

JIT が実行する可能性のあるあらゆる種類の最適化を回避するために、間接的に呼び出す項目をいくつか作成しました。

結果はかなり魅力的です!

47.172 ミリ秒で 1,000,000 回のアクション コール
12,035.943 ミリ秒で 1,000,000 回の Delegate.DynamicInvoke 呼び出し

44.686 ミリ秒で 1,000,000 回のアクション コール
1,000,000 回の Delegate.DynamicInvoke 呼び出し (12,318.846 ミリ秒)

したがって、この場合、DynamicInvoke追加の間接呼び出しとキャストを への呼び出しに置き換えると、約270 倍速くなりました。今日の仕事すべて。

于 2009-07-13T10:57:22.127 に答える
1

これを、Reflection.Emit を使用せずにクラスからのメンバー呼び出しをラップするように拡張する必要がある場合は、クラスと関数のパラメーター リストまたは戻り値の型をマップできる一連のコンパイラ ヒントを作成することで実行できます。

基本的に、オブジェクトをパラメーターとして受け取り、オブジェクトを返すラムダを作成する必要があります。次に、コンパイラが AOT を参照する汎用関数を使用して、適切なメソッドのキャッシュを作成し、メンバーを呼び出してパラメーターをキャストします。秘訣は、オープン デリゲートを作成し、それらを 2 番目のラムダに渡して、実行時に基になるヒントに到達することです。

すべてのクラスとシグネチャにヒントを提供する必要があります (ただし、すべてのメソッドまたはプロパティではありません)。

ここでこれを行うクラスを作成しましたが、この投稿にリストするには少し長すぎます。

パフォーマンス テストでは、上記の例ほど良くはありませんが、必要な状況で動作することを意味する一般的なものです。Invoke と比較して、プロパティの読み取りで約 4.5 倍のパフォーマンス。

于 2012-04-18T21:30:22.947 に答える