DataBinder и скорость

    Во многих проектах есть необходимость доступа к свойствам объектов используя механизм System.Web.UI.DataBinder.Eval и встал вопрос: насколько это быстро работает и можно ли выполнять такую операцию еще быстрее?

    Итак, был сделан небольшой тест. Для начала сделали 2 класса (для тестирования виртуальных и переопределенных свойств), к свойствам которых будем обращаться:
      public class c1
      {
        string _str;
        public c1(string str)
        {
          _str        = str;
        }
        public string Str1     { get { return "1: "+_str; } }
        public virtual string Str2 { get { return "2: "+_str; } }
      }

      public class c2 : c1
      {
        public c2(string str)
          : base(str)
        {
        }
        public override string Str2 { get { return "C"+base.Str2; } }
      }

    Теперь собственно тест:
    c2 c        = new c2("str");

    // Прямой доступ к свойствам
    for (int i=0; i<ITERATIONS; i++)
    {
      String.Format("Str1: {0}, Str2: {1}", c.Str1, c.Str2);
    }
    // Доступ через DataBinder
    for (int i=0; i<ITERATIONS; i++)
    {
      String.Format("Str1: {0}, Str2: {1}", DataBinder.Eval(c, "Str1"), DataBinder.Eval(c, "Str2"));
    }

    Результат:
    • Прямой вызов: 00:00:00.065 (100%)
    • DataBinder.Eval: 00:00:01.135 (1746%)

    Ничего себе, в 17 раз медленнее! Хотя что-то похожее и предполагалось.
    Для увеличения скорости сначала было предложено кэшировать обращения к Reflection (куда же без него в такой задаче), но серьезного ускорения это не дало. Тогда был опробован подход с генерацией кода обращения на лету, то есть:
    1. При первом обращении изучаем класс и свойство через Reflection
    2. Идем к тому классу, где свойство было определено или переопределено
    3. Строим класс, который состоит из одного метода: получить значение нужного свойства у объекта определенного типа
    4. Вызываем построенный метод

    Код генерации приводить в топике не буду, кому интересно скачивайте Проект (29Кб).
    c2 c        = new c2("str");
    PropertyAccessor p1  = PEval.GetPropertyAccessor(c.GetType(), "Str1");
    PropertyAccessor p2  = PEval.GetPropertyAccessor(c.GetType(), "Str2");
    for (int i=0; i<ITERATIONS; i++)
    {
      String.Format("Str1: {0}, Str2: {1}", p1.GetValue( c ), p2.GetValue( c ));
    }

    Результат выполнения:
    • Кэшированный вызов: 00:00:00.065 (100%)

    Отличный результат! По скорости сопоставим с прямым вызовом, при нескольких запусках это время иногда было меньше 100%. Вызов назвал кэшированным так как класс с методом был запомнен и шло постоянное обращение без проверки существования такого класса.
    Для завершения эксперимента был написан статический класс, который кэшировал построенные классы и создавал их по мере надобности.
    c2 c        = new c2("str");
    for (int i=0; i<ITERATIONS; i++)
    {
      String.Format("Str1: {0}, Str2: {1}", PEval.GetValue(c, "Str1"), PEval.GetValue(c, "Str2"));
    }

    Результат выполнения:
    • PEval.GetValue: 00:00:00.090 (138%)

    Всего на 38% медленнее прямого вызова. Несколько запусков дали средний результат 150%, но все равно это не 1746%.
    Полный вывод теста:
    Create PropertyAccessor for TestDataBinding.c1.Str1
    Create PropertyAccessor for TestDataBinding.c2.Str2
    =========================
    ITERATIONS: 100000
    Прямой вызов: 00:00:00.065 (100%)
    PEval.GetValue: 00:00:00.090 (138%)
    Кэшированный вызов: 00:00:00.065 (100%)
    DataBinder.Eval: 00:00:01.135 (1746%)
    =========================
    TestDataBinding.c2 (TestDataBinding.PropEvaluator.PropertyAccessor) [200003]
    TestDataBinding.c1_Str1_Accessor => Str1 (G) [100002]
    TestDataBinding.c2_Str2_Accessor => Str2 (G) [100001]
    TestDataBinding.c1 (TestDataBinding.PropEvaluator.PropertyAccessor) [2]
    TestDataBinding.c1_Str1_Accessor => Str1 (G) [100002]
    =========================


    2 миллиона вызовов:
    =========================
    ITERATIONS: 1000000
    Прямой вызов: 00:00:00.930 (100%)
    PEval.GetValue: 00:00:01.085 (116%)
    Кэшированный вызов: 00:00:00.738 (79%)
    DataBinder.Eval: 00:00:10.976 (1180%)
    =========================


    И еще в 10 раз больше:
    =========================
    ITERATIONS: 10000000
    Прямой вызов: 00:00:06.802 (100%)
    PEval.GetValue: 00:00:10.917 (160%)
    Кэшированный вызов: 00:00:07.017 (103%)
    DataBinder.Eval: 00:01:45.476 (1550%)
    =========================


    Что еще можно добавить:
    1. Сделать методы для изменения значения свойства
    2. Расширить класс доступа к свойству дополнительными функциями: отображаемое имя свойства, форматирование значения в строку/парсинг значения из строки, валидация значения
    3. Сделать вызов не одного свойства, а цепочки PEval.GetValue(o, "Prop1.Prop2.Prop4")


    Дополнение 1.
    Диаграмма классов более продвинутой версии:
    Диаграмма классов для доступа к свойствам

    Диаграмма классов генератора кода:
    image
    Поделиться публикацией

    Похожие публикации

    Комментарии 15
      0
      здорово, хороший материал и статья написана хорошо

      предложение: создайте проект на codeplex с библиотекой быстрого eval, думаю будет очень популярным проектом, я бы, например, пользовался
          –1
          там есть реализация быстрого Eval?
          не нашел
            0
            там есть реализация быстрого доступа к свойствам/методам типов.
            могу выложить свой ReflectionHelper который использует его
            0
            Посмотрел проект по ссылке — есть разница в подходе: в указанном проекте создаются делегаты и потом используются, я же создаю объект доступа к свойству, в котором есть методы чтения/установки значения и можно добавить любой дополнительный код для работы с этим свойством. Плюс у меня реализована практически готовая к использованию инфраструктура кэширования построенных классов (да еще и потокобезопасная), а так же есть оптимизация количества построенных классов: если в дочернем классе свойство не переопределялось, то создается только один объект доступа.
            0
            Спасибо. Мой описанный подход используют несколько коллег, но выставить проект полностью я пока не готов, тем более что этот пример входит в более обширный проект и там уже идет использование в контролах ввода.
            –1
            Как я понял это всё для asp.net.
            Я не вижу смысла в такой оптимизации, тк приходится писать лишний код. В asp.net есть outputcache и он в любом случае сведёт разницу между обычным биндингом и вашим методом на нет.
              –1
              Это можно применять и не для ASP.NET, у меня такой подход используется и в обычных приложениях.
              Так же не стоит путать теплое с мягким: кэшироание вывода и ускорение обращения — это разные вещи и не всегда возможно использовать кэширование вывода, а вот от использования быстрого evalа хуже не будет.
              0
              Отличная статья!

              Скажите, а ((Model)Container.DataItem).PropertyName вы не пробовали? Я обычно использую именно так, избегая Eval, т.к. при этом получаем всю мощь рефакторинга и не работаем со строками. Интересны отличия и в плане производительности, если можете, добавьте в тесты.

              Спасибо!
                –1
                Конечно пробовал, это просто прямое обращение к свойству :). Есть ряд задач, когда от eval не отвертеться.
                  0
                  Извиняюсь, посмотрите, пожалуйста, на последний коммент в ветке, промазал :(
                0
                А, это оно в виду и имелось, ясно. А в каких задачах вам от Eval не отвертеться, я как-то не натыкался?
                  0
                  Самое простое — привязать поле ввода к свойству объекта, тут в ASPX пишем <my: control prop=«PropertyName»>.
                  Более сложное — при генерации документов по шаблонам, но тут без длинного рассказа о том, как генерация работает смысла не понять.

                  Вообще нужно везде, где есть какой-то класс и нет возможности обеспечить прямое обращение к свойству, то приходится использовать eval.
                  0
                  можно код?
                    0
                    Так это, ссылка в топике: dema.ru/sources/TestDataBinding.7z а если нужна более продвинутая версия, то ее еще нужно готовить (вытащить из большой библиотеки).

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое