Ускорение Отражение Invoke С#/. NET

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

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

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

и здесь:

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



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

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

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, но я стараюсь избегать этого.

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

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

Ответы

Ответ 1

Используете ли вы С# 4? Если это так, dynamic может ускорить работу:

Action<object> hook = message => ((dynamic)handler).HandleMessage((dynamic)message);

Ответ 2

Использование 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.

Ответ 3

Вы можете использовать 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!");
    }
}

Ответ 4

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

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

Ответ 5

Если вы знаете подпись, используйте 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;
}