Autofac: Советы по повышению производительности при использовании DynamicProxy? - PullRequest
4 голосов
/ 10 марта 2011

Я только начал использовать DynamicProxy2 сегодня.И обнаружил, что это привело к значительному падению производительности.

См. Код ниже.Test1 в 10 раз медленнее, чем Test2.

Есть ли какие-либо советы по повышению производительности при использовании 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());
        }
    }

Ответы [ 2 ]

0 голосов
/ 27 марта 2011

Не ответ, но думал, что я добавлю свой ввод.

Вместо использования расширений 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 секунд для меня).

Мне было интересно, если, как предложил Кшиштоф, AutofacContrib.DynamicProxy2 делал что-то наивное, например, пытался каждый раз создавать новый ProxyGenerator.Но когда я попытался эмулировать это вручную, я получил исключение OOM (у меня только 2 гигабайта на этой машине).

0 голосов
/ 10 марта 2011

Какие номера вы получаете? Заметно ли падение производительности в реальной жизни?

Я не знаком с тем, как Autofac использует DP внутри, но вы не должны заметить большого влияния на производительность.

Контейнеру необходимо проделать дополнительную работу, чтобы прокси-сервер виртуальной машины, создать экземпляр перехватчика (то есть вы создаете два объекта вместо одного) и присоединить перехватчик к прокси.

Если кэширование используется правильно, вы получите одноразовый удар по производительности, когда DP фактически генерирует тип прокси. Затем тип следует использовать повторно.

Вы можете легко проверить это, проверив тип последнего возвращенного прокси.

Если это Castle.Proxies.TestViewModelProxy, это означает, что кэширование работает нормально.

Если это Castle.Proxies.TestViewModelProxy_1000000, то вы каждый раз генерируете новый тип прокси, что, очевидно, снижает вашу производительность.

В целом влияние производительности должно быть пренебрежимо по стандартам реальной жизни.

...