2011-08-25 6 views
15

Ci sono un sacco di posti per l'accelerazione riflessione invoca, esempi qui:.Accelerare Riflessione Invoke C#/NET

Speeding up Reflection API with delegate in .NET/C#

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

e qui:

Example : Speeding up Reflection API with delegate in .NET/C#



La mia domanda riguarda l'accelerazione dei richiami generici. Ciò è effettivamente possibile?

Ho una classe astratta e una classe che implementa ...

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++; } 
} 

Quello che voglio fare è costruire un elenco di queste classi di gestione dei messaggi e rapidamente richiamare la loro HandleMessage()


al momento, sto facendo qualcosa che è circa questo:

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()); 

Questo non è il tutto, ma è le cose importanti ...

Il method.Invoke è molto lento, mi piacerebbe mantenere i parametri generici sulla classe, mi rendo conto che avrei potuto bloccare questo fino a oggetto e gettarlo nel metodo HandleMessage, ma sto cercando di evitare di farlo.

C'è qualcosa che posso fare per velocizzare questo? Attualmente gli ordini di grandezza sono più lenti delle chiamate dirette.

Qualsiasi aiuto sarebbe apprezzato.

risposta

7

Stai utilizzando C# 4? Se è così, dynamic può accelerare le cose:

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

Sembra lanciare RuntimeBinderException: la migliore corrispondenza del metodo sovraccarico per "X" ha alcuni argomenti non validi. – Rob

+0

@Rob: Che dire di '((dynamic) handler) .HandleMessage ((dynamic) message)'? – Gabe

+0

Questo funziona! :) Woot! – Rob

0

No, non è (purtroppo) possibile. Reflection è lento e MethodInfo.Invoke() non fa eccezione. Non potresti usare interfacce (generiche) e quindi chiamare direttamente?

Modifica aggiornamento: Una cosa viene in mente per accelerare davvero questo, ma l'overhead di codifica è enorme: è possibile utilizzare la generazione e la compilazione del codice dinamico. Ciò significherebbe creare dinamicamente il codice sorgente che chiamerebbe il metodo senza riflessione, compilandolo e eseguendolo dinamicamente. Ciò significherebbe un impatto inizialmente sulle prestazioni per la creazione e la compilazione delle classi che svolgono il tuo lavoro, ma poi hai chiamate dirette per ogni chiamata successiva.

+0

E * è * possibile. –

+0

Perché il sovraccarico della codifica è "massiccio"? Mi aspetterei solo un paio di righe di codice usando 'Expression'. – Gabe

+0

Per prima cosa, quando si usano espressioni (o CodeDom) è minaccioso nella manutenibilità. Anche il debug/validazione del codice generato è più difficile. È meglio generare codice C# al volo e compilarlo. Questo ti offre un controllo migliore sul controllo generato. –

8

Utilizzare Delegate.CreateDelegate() dovrebbe essere molto più veloce. Si finirà con un puntatore alla funzione reale, non un delegato che chiama Invoke().

Prova questo:

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 }); 

Nella stessa classe aggiungere il seguente metodo generico. Invochiamo questo dinamicamente sopra perché non conosciamo il parametro type in fase di compilazione.

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); 
} 

quindi si ha un delegato che getta il parametro al tipo desiderato, quindi chiama il metodo HandleMessage.

+1

Batti ad esso, ma dovresti usare l'overload 'MethodInfo' per abbinare la sua domanda più da vicino. –

+0

Se guardi la mia risposta vedrai che il suo problema è un po 'più complicato di questo (a causa dei parametri generici). –

+0

Grazie Jonathan. Posso vedere qual è il problema.Ho modificato il mio per mostrare un'altra soluzione che non coinvolge espressioni. Spero che quello funzioni. –

6

È possibile utilizzare Delegate::CreateDelegate. Questo è significativamente più veloce di 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()); 

Sentitevi liberi di punto di riferimento, ma io sono in panchina prima ed è stato significativamente più veloce.

Modifica: Vedo il tuo problema ora, non puoi farlo nel modo che ho suggerito.

È possibile utilizzare espressioni per compilare un delegato che fa l'invoke per voi, questo sarà molto veloce:

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()); 

Edit: A parte il mio esempio di espressione di cui sopra, il seguente funziona anche - ed è quello che faccio in questi tipi di scenari.

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

Sembra che lanci una ArgumentException: Errore durante il binding al metodo target. l'oggetto non sembra legarsi all'argomento generico. – Rob

+0

@Rob guarda la modifica, vedo il tuo problema ora. –

+0

Sembra non funzionare sulla linea: var lambda = Expression.Lambda > (ifExpr, instExpr, paramExpr); ---- ParameterExpression di tipo 'MyMessageType' non può essere utilizzato per il parametro delegato di tipo 'Message' ---- Questa è stata la prima modifica btw. – Rob

0

Se si conosce la firma, utilizzare Delegate.CreateDelegate.

Se non si conosce la firma, è molto difficile ottenere qualcosa che sia veloce. Se hai bisogno di velocità, allora qualunque cosa tu faccia, cerca di evitare Delegate.DynamicInvoke che è estremamente lento.

(nota che "slow" è molto relativo qui. Assicurati di aver davvero bisogno di ottimizzare questo DynamicInvoke è qualcosa come 2,5 milioni di invocazioni al secondo (sulla mia macchina), che è molto probabile abbastanza veloce. più simile a 110+ milioni di invocazioni al secondo ed è più veloce di Method.Invoke.)

ho trovato an article che discute un modo per farlo (invocare un metodo veloce senza conoscere la firma al momento della compilazione). Ecco la mia versione dell'implementazione. Il problema strano è che potresti creare un lambda che rappresenta l'invocazione, ma non conosceresti la firma di quel lambda e dovresti chiamarlo dinamicamente (lentamente). Invece, è possibile cuocere l'invocazione fortemente tipizzata nella lambda, con la lambda che rappresenta l'atto di invocazione piuttosto che il metodo specifico stesso. (Il lambda finisce per essere un Func<object, object[], object> in cui si passa un oggetto e alcuni valori e tornare il valore di ritorno.)

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; 
}