5

今日、DynamicProxy2 を使い始めました。そして、それが大幅なパフォーマンスの低下を引き起こしていることがわかりました。

以下のコードを参照してください。Test1 は Test2 よりも 10 倍遅いです。

DynamicProxy を使用するときにパフォーマンスを向上させるためのヒントはありますか?

class Program
{
    public void Main()
    {
        for (int i = 0; i < 3; i++)
        {
            var stopWatch = Stopwatch.StartNew();
            int count = 1 * 1000 * 1000;

            Test1(count);
            //Test2(count);

            long t = stopWatch.ElapsedMilliseconds;
            Console.WriteLine(t.ToString() + " milliseconds");
            Console.WriteLine(((double)count/(t/1000)).ToString() + " records/1 seconds");
        }
    }

    void Test1(int count)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<TestViewModel>()
            .EnableClassInterceptors()
            .InterceptedBy(typeof(NotifyPropertyChangedInterceptor));
        builder.RegisterType<NotifyPropertyChangedInterceptor>();

        var container = builder.Build();
        for (int i = 0; i < count; i++)
        {
            container.Resolve<TestViewModel>();
        }
    }

    void Test2(int count)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<TestViewModel>();

        var container = builder.Build();
        for (int i = 0; i < count; i++)
        {
            container.Resolve<TestViewModel>();
        }
    }
}

public class TestViewModel : INotifyPropertyChanged
{
    [Notify]
    public virtual string Value { get; set; }
    public event PropertyChangedEventHandler PropertyChanged;
}

/// <summary>
/// Copied from: http://serialseb.blogspot.com/2008/05/implementing-inotifypropertychanged.html
/// </summary>
public class NotifyPropertyChangedInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        // let the original call go through first, so we can notify *after*
        invocation.Proceed();
        if (invocation.Method.Name.StartsWith("set_"))
        {
            string propertyName = invocation.Method.Name.Substring(4);
            var pi = invocation.TargetType.GetProperty(propertyName);

            // check that we have the attribute defined
            if (Attribute.GetCustomAttribute(pi, typeof(NotifyAttribute)) == null)
                return;

            // get the field storing the delegate list that are stored by the event.
            FieldInfo info = invocation.TargetType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
                .Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
                .FirstOrDefault();

            if (info != null)
            {
                // get the value of the field
                PropertyChangedEventHandler evHandler = info.GetValue(invocation.InvocationTarget) as PropertyChangedEventHandler;
                // invoke the delegate if it's not null (aka empty)
                if (evHandler != null)
                    evHandler.Invoke(invocation.TargetType, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

アップデート:

私のマシンでは、Test1 に約 45 秒、Test2 に約 4.5 秒かかります。Krzysztof Koźmicの回答を読んだ後、 NotifyPropertyChangedInterceptorをシングルトン スコープに入れようとしました。

builder.RegisterType<NotifyPropertyChangedInterceptor>().SingleInstance();

約4秒節約できました。Test1 には約 41 秒かかります。

更新 2:

私のマシンでは、Test3 に約 8.3 秒かかります。そのため、Autofac または DynamicProxy を単独で使用してもパフォーマンスはそれほど大きな問題ではないようですが (私のプロジェクトでは)、それらを組み合わせるとパフォーマンスが大幅に低下します。

    public void Test3(int count)
    {
        var generator = new Castle.DynamicProxy.ProxyGenerator();
        for (int i = 0; i < count; i++)
        {
            generator.CreateClassProxy(typeof(TestViewModel), 
                new NotifyPropertyChangedInterceptor());
        }
    }
4

2 に答える 2

0

答えではありませんが、入力を追加すると思いました。

AutofacContrib.DynamicProxy2 拡張機能を使用する代わりに、プロキシを手動で構築するようにコンテナーを設定しようとしたため、Test1 は次のようになります。

    void Test1(int count)
    {
        var builder = new ContainerBuilder();

        ProxyGenerator pg = new ProxyGenerator();
        builder.Register(c => 
        {
            var obj = pg.CreateClassProxyWithTarget(new TestViewModel(), c.Resolve < NotifyPropertyChangedInterceptor>());
            return (TestViewModel)obj;
        });
        builder.RegisterType<NotifyPropertyChangedInterceptor>().SingleInstance();


        var container = builder.Build();
        for (int i = 0; i < count; i++)
        {
            container.Resolve<TestViewModel>();
        }
    }

これは、私のマシンでは約 13.5 秒で実行されるようです (参考までに、元のテストにも約 45 秒かかります)。

Krzysztof が示唆したように、AutofacContrib.DynamicProxy2 は毎回新しい ProxyGenerator を作成しようとするような素朴なことをしているのではないかと思っていました。しかし、これを手動でエミュレートしようとすると、OOM 例外が発生しました (ただし、このマシンには 2 ギガしかありません)。

于 2011-03-26T23:53:34.997 に答える
0

どのような種類の数値が得られますか? 実際の使用でパフォーマンスの低下は顕著ですか?

Autofac が内部で DP をどのように使用しているかはよくわかりませんが、パフォーマンスに大きな影響を与えることはありません。

コンテナーは、VM をプロキシし、インターセプターをインスタンス化して (つまり、1 つではなく 2 つのオブジェクトを作成する)、インターセプターをプロキシーにアタッチするために、より多くの作業を行う必要があります。

キャッシュが正しく使用されている場合、DP が実際にプロキシ タイプを生成しているときに、1 回限りのパフォーマンス ヒットが発生します。その後、型を再利用する必要があります。

返された最後のプロキシのタイプを調べることで、簡単に確認できます。

その場合Castle.Proxies.TestViewModelProxyは、キャッシュが正常に機能することを意味します。

その場合、Castle.Proxies.TestViewModelProxy_1000000毎回新しいプロキシ タイプを生成しているため、パフォーマンスが当然低下します。

一般に、パフォーマンスへの影響は、実際の基準では無視できるはずです。

于 2011-03-10T08:52:12.070 に答える