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,实例化拦截器(因此您要创建两个对象而不是一个)并将拦截器与代理连接。

如果缓存使用得当,当 DP 实际生成代理类型时,您将获得一次性性能损失。然后应该重用该类型。

您可以通过检查返回的最后一个代理的类型轻松检查。

如果是这样Castle.Proxies.TestViewModelProxy,则意味着缓存可以正常工作。

如果是这样,Castle.Proxies.TestViewModelProxy_1000000那么您每次都会生成一个新的代理类型,这可以理解地降低您的性能。

一般来说,按照现实生活标准,性能影响应该可以忽略不计。

于 2011-03-10T08:52:12.070 回答