Почему PropertyInfo SetValue и GetValue настолько медленны?

Почему методы PropertyInfo для получения и установки свойства настолько медленны? Если я построю делегат с помощью Reflection.Emit, он будет намного быстрее.

Они делают что-то важное, так что время, которое они принимают, может быть оправдано? То есть... я что-то упустил, используя Reflection.Emit для создания делегатов вместо использования GetValue и SetValue PropertyInfo (кроме скорости разработки)?

PS: Пожалуйста, дайте показания, а не только гадать!

Ответы

Ответ 1

Реализация RuntimePropertyInfo (который является конкретным подклассом PropertyInfo для типов времени выполнения) реализует GetValue и SetValue путем вызова методов getter и setter посредством отражения (MethodInfo.Invoke), тогда как ваш сгенерированный делегат вероятно, вызывает методы напрямую. Поэтому вопрос сводится к: почему RuntimeMethodInfo.Invoke настолько медленный по сравнению с скомпилированным вызовом?

Когда вы декомпилируете (или смотрите исходные источники для) RuntimeMethodInfo.Invoke, вы можете видеть, что это, вероятно, потому, что Invoke выполняет множество задач:

  • он выполняет проверки согласованности (передают ли количество и типы передаваемых параметров подписи? прошел ли экземпляр, соответствующий типу объявления?), был ли экземпляр передан, хотя метод является статическим?),
  • он выполняет видимость и (если проверка видимости обходится) проверки безопасности,
  • он разворачивает массив параметров, обрабатывая параметры ref особым образом, чтобы их можно было записать позже,
  • при необходимости он отключает параметры,
  • ему нужно найти указатель метода на основе дескриптора типа времени выполнения и дескриптора метода, связанного с RuntimeMethodHandle, а затем вызвать метод
  • при необходимости он возвращает возвращаемое значение и
  • он помещает и помещает в массив параметров любые параметры ref/out.

Среда выполнения будет выполнять аналогичные проверки согласованности, безопасности и видимости при компиляции вашего делегата в исполняемый собственный код. Он также испускает код для бокса/распаковки и т.д. Однако он должен только один раз выполнять эти действия и затем может гарантировать, что код будет безопасным для выполнения. Это приводит к тому, что фактический метод вызывает очень дешевую операцию (загружайте параметры и переходите к адресу метода).

Напротив, каждый вызов RuntimeMethodInfo.Invoke (и, следовательно, GetValue/SetValue) должен повторять всю работу, поскольку контекст - параметры, экземпляр и использование возвращаемого типа - неизвестен. Вероятно, поэтому это так медленно.

О том, что вам может не хватать: если вы испускаете своих собственных делегатов вызова свойств, вам, конечно же, нужно иметь дело с бокс/распаковкой, параметрами ref/out и т.д. самостоятельно.

Ответ 2

Нет необходимости использовать Emit. Экспрессию гораздо проще использовать. Вы можете ускорить доступ, как описано в fooobar.com/info/348897/.... Класс-помощник создает "метод-указатель" (Action/Func) для получателя или сеттера. Если вы повторно используете Action/Func, вы сможете выполнять так же быстро, как обычный сеттер.

   // creating setter (once)
   var propertyInfo = typeof(T).GetProperty(field);
   var setter = FastInvoke.BuildUntypedSetter<T>(propertyInfo));

   // usage somehow later in a loop of data
   foreach(var myobject in MySource)
   {
     setter(myobject, myValue)
   }