Ускорение отражения Invoke C # /. NET - PullRequest
15 голосов
/ 25 августа 2011

Существует множество постов по ускорению работы отражений, примеры здесь:

Ускорение Reflection API с делегатом в .NET / C #

https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/

и здесь:

Пример: ускорение API отражения с делегатом в .NET / C #



Мой вопрос касается ускорения общих вызовов. Возможно ли это вообще?

У меня есть абстрактный класс и класс, который его реализует ...

public abstract class EncasulatedMessageHandler<T> where T : Message
{
    public abstract void HandleMessage(T message);
}

public class Handler : EncasulatedMessageHandler<MyMessageType>
{
    public int blat = 0;
    public override void HandleMessage(MyMessageType message) { blat++; }
}

То, что я хочу сделать, это создать список этих классов обработчиков сообщений и быстро вызвать их HandleMessage ()


В данный момент я делаю что-то примерно такое:

object handler = Activator.CreateInstance(typeof(Handler)); // Ignore this, this is done up front.

MethodInfo method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);

Action<object> hook = new Action<object>(delegate(object message)
{
    method.Invoke(handler, new object[] { message });
});

// Then when I want to invoke it:

hook(new MyMessageType());

Это не все, но это важные вещи ...

Метод. Invoke очень медленный, я хотел бы сохранить общие параметры в классе, я понимаю, что могу привязать это к объекту и привести к нему в методе HandleMessage, но я пытаюсь избежать этого .

Что я могу сделать, чтобы ускорить это? В настоящее время он на несколько порядков медленнее, чем прямые звонки.

Любая помощь будет оценена.

Ответы [ 5 ]

8 голосов
/ 25 августа 2011

Использование Delegate.CreateDelegate() должно быть намного быстрее. В итоге вы получите указатель на реальную функцию, а не делегат, который вызывает Invoke().

Попробуйте это:

object handler = Activator.CreateInstance(typeof(Handler)); 
var handlerType = handler.GetType();
var method = handlerType.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);
var paramType = handlerType.GetGenericArguments()[0];

// invoke the MakeHandleMessageDelegate method dynamically with paramType as the type parameter
// NB we're only doing this once
Action<object> hook = (Action<object>) this.GetType().GetMethod("MakeHandleMessageDelegate")
            .MakeGenericMethod(paramType)
            .Invoke(null, new [] { handler });

В том же классе добавьте следующий универсальный метод. Мы вызываем это динамически выше, потому что мы не знаем параметр типа во время компиляции.

public static Action<object> MakeHandleMessageDelegate<T>(object target)
{
    var d = (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), target, "HandleMessage");

    // wrap the delegate another that simply casts the object parameter to the required type
    return param => d((T)param);
}

Затем у вас есть делегат, который преобразует параметр в требуемый тип, а затем вызывает метод HandleMessage.

7 голосов
/ 25 августа 2011

Вы используете C # 4? Если это так, dynamic может ускорить процесс:

Action<object> hook = message => ((dynamic)handler).HandleMessage((dynamic)message);
6 голосов
/ 25 августа 2011

Вы можете использовать Delegate::CreateDelegate. Это значительно быстрее, чем Invoke().

var handler = Activator.CreateInstance(typeof(Handler));
var method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);
var hook = (Action<object>)Delegate.CreateDelegate(typeof(Action<object>), handler, method);

// Then when you want to invoke it: 
hook(new MyMessageType()); 

Не стесняйтесь тестировать его, но я уже тестировал его, и он был значительно быстрее.

Редактировать : Теперь я вижу вашу проблему, вы не можете сделать это так, как я предлагал.

Вы можете использовать выражения для компиляции делегата, который выполняет вызов для вас, это будет очень быстро:

var type = typeof(Handler);
var instance = Activator.CreateInstance(type);
var method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);

var originalType = type;
// Loop until we hit the type we want.
while (!(type.IsGenericType) || type.GetGenericTypeDefinition() != typeof(EncasulatedMessageHandler<>))
{
    type = type.BaseType;
    if(type == null)
        throw new ArgumentOutOfRangeException("type");
}

var messageType = type.GetGenericArguments()[0]; // MyMessageType

// Use expression to create a method we can.
var instExpr = Expression.Parameter(typeof(object), "instance");
var paramExpr = Expression.Parameter(typeof(Message), "message");
// (Handler)instance;
var instCastExpr = Expression.Convert(instExpr, originalType);
// (MyMessageType)message
var castExpr = Expression.Convert(paramExpr, messageType); 
// ((Handler)inst).HandleMessage((MyMessageType)message)
var invokeExpr = Expression.Call(instCastExpr, method, castExpr); 
// if(message is MyMessageType) ((Handler)inst).HandleMessage((MyMessageType)message);
var ifExpr = Expression.IfThen(Expression.TypeIs(paramExpr, messageType), invokeExpr);

// (inst, message) = { if(message is MyMessageType) ((Handler)inst).HandleMessage((MyMessageType)message); }
var lambda = Expression.Lambda<Action<object, Message>>(ifExpr, instExpr, paramExpr);
var compiled = lambda.Compile();
Action<Message> hook = x => compiled(instance, x);

hook(new MyMessageType());

Редактировать: Помимо приведенного выше примера Expression, будет работать и следующее - и это то, что я делаю в этих типах сценариев.

var instance = (IEncapsulatedMessageHandler)Activator.CreateInstance(typeof(Handler));
instance.HandleMessage(new MyMessageType());

public class Message { }

public class MyMessageType : Message { }

public interface IEncapsulatedMessageHandler
{
    void HandleMessage(Message message);
}

public abstract class EncasulatedMessageHandler<T> : IEncapsulatedMessageHandler where T : Message
{
    public abstract void HandleMessage(T message);

    void IEncapsulatedMessageHandler.HandleMessage(Message message)
    {
        var msg = message as T;
        if (msg != null)
            HandleMessage(msg);
    }
}

public class Handler : EncasulatedMessageHandler<MyMessageType>
{
    public override void HandleMessage(MyMessageType message)
    {
        Console.WriteLine("Yo!");
    }
}
0 голосов
/ 27 октября 2017

Если вы знаете подпись, используйте Delegate.CreateDelegate.

Если вы не знаете подпись, очень сложно получить что-то такое быстрое.Если вам нужна скорость, то, что бы вы ни делали, старайтесь избегать Delegate.DynamicInvoke, который чрезвычайно медленный.

(Обратите внимание, что «медленный» здесь очень относительный. Убедитесь, что вам действительно нужно оптимизировать это. DynamicInvokeэто примерно 2,5 миллиона вызовов в секунду (на моей машине), что, скорее всего, достаточно быстро. Реализация ниже более похожа на 110+ миллионов вызовов в секунду и быстрее, чем Method.Invoke.)

Я обнаружил статья , в которой обсуждается способ сделать это (быстро вызвать метод, не зная сигнатуру во время компиляции).Вот моя версия реализации.Странная проблема заключается в том, что вы можете создать лямбду, которая представляет вызов, но вы не будете знать сигнатуру этой лямбды и должны будете вызывать ее динамически (медленно).Но вместо этого вы можете испечь строго типизированный вызов в лямбду, причем лямбда представляет собой акт вызова, а не конкретный метод.(Лямбда заканчивается как Func<object, object[], object>, где вы передаете объект и некоторые значения и возвращаете возвращаемое значение.)

public static Func<object, object[], object> ToFastLambdaInvocationWithCache(
   this MethodInfo pMethodInfo
) {
   Func<object, object[], object> cached;
   if (sLambdaExpressionsByMethodInfoCache.TryGetValue(pMethodInfo, out cached))
      return cached;

   var instanceParameterExpression = Expression.Parameter(typeof(object), "instance");
   var argumentsParameterExpression = Expression.Parameter(typeof(object[]), "args");

   var index = 0;
   var argumentExtractionExpressions =
      pMethodInfo
      .GetParameters()
      .Select(parameter =>
         Expression.Convert(
            Expression.ArrayAccess(
               argumentsParameterExpression,
               Expression.Constant(index++)
            ),
            parameter.ParameterType
         )
      ).ToList();

   var callExpression = pMethodInfo.IsStatic
      ? Expression.Call(pMethodInfo, argumentExtractionExpressions)
      : Expression.Call(
         Expression.Convert(
            instanceParameterExpression, 
            pMethodInfo.DeclaringType
         ),
         pMethodInfo,
         argumentExtractionExpressions
      );

   var endLabel = Expression.Label(typeof(object));
   var finalExpression = pMethodInfo.ReturnType == typeof(void)
      ? (Expression)Expression.Block(
           callExpression,
           Expression.Return(endLabel, Expression.Constant(null)), 
           Expression.Label(endLabel, Expression.Constant(null))
        )
      : Expression.Convert(callExpression, typeof(object));

   var lambdaExpression = Expression.Lambda<Func<object, object[], object>>(
      finalExpression,
      instanceParameterExpression,
      argumentsParameterExpression
   );
   var compiledLambda = lambdaExpression.Compile();
   sLambdaExpressionsByMethodInfoCache.AddOrReplace(pMethodInfo, compiledLambda);
   return compiledLambda;
}
0 голосов
/ 25 августа 2011

Нет, это (к сожалению) невозможно.Отражение медленное , и MethodInfo.Invoke () не является исключением.Не могли бы вы использовать (универсальные) интерфейсы и, следовательно, прямые вызовы?

Редактировать обновление: одна вещь приходит на ум, чтобы действительно ускорить это, но накладные расходы на кодирование огромны: вы можете использовать динамическую генерацию и компиляцию кода.Это будет означать динамическое создание исходного кода, который будет вызывать метод без отражения, динамическую компиляцию и выполнение этого.Это будет означать первоначально влияние на производительность для создания и компиляции классов, которые выполняют вашу работу, но затем у вас будут прямые вызовы для каждого последующего вызова.

...