2012-01-21 17 views
6

Sto costruendo una mappa di invio messaggi in C# e principalmente sto solo giocando con approcci diversi. Sono curioso di una differenza di prestazioni che sto misurando, ma non è ovvio perché osservando l'IL.Perché il cast di un tipo generico è più lento di un cast esplicito in C#?

La mappa messaggio:

delegate void MessageHandler(Message message); 
AddHandler(Type t, MessageHandler handler) 
{ 
    /* add 'handler' to messageMap invocation list */ 
} 

delegate void GenericMessageHandler<T>(T message); 
AddHandler<T>(GenericMessageHandler<T> handler) where T: Message 
{ 
    AddHandler(typeof(T), e => { handler((T)e); }); 
} 

Dictionary<Type, MessageHandler> messageMap; 

Ho poi hanno una gerarchia di classi di messaggi, simili a EventArgs in WPF, ad esempio:

public class Message {} 
public class VelocityUpdateMessage : Message 

e osservatori classi con funzioni di gestione:

void HandleVelocityUpdate(VelocityUpdateMessage message) { ... } 

Sto misurando 2 modi di aggiungere i gestori invocando &. Sto avvolgendo la chiamata del delegato in modo da ottenere un po 'di sicurezza concettuale e qui sta la differenza perfetta.

Approccio 1: ascoltatore chiama

AddHandler(typeof(VelocityUpdateMessage), 
      e => { HandleVelocityUpdate((VelocityUpdateMessage)e); }); 

Approccio 2: ascoltatore chiama

AddHandler<VelocityUpdateMessage>(HandleVelocityUpdate); 

Entrambi gli approcci costruire un delegato messageHandler che fa un cast e lo stesso metodo di chiamata, ma chiamando i delegati costruita utilizzando l'approccio # 2 è un po 'più lento anche se l'IL generato sembra identico. È un overhead di runtime aggiuntivo nel cast di un tipo generico? È il vincolo del tipo? Mi aspetto che i delegati JITted siano gli stessi una volta risolto il tipo generico.

Grazie per qualsiasi informazione.

+2

Come stai misurando? Questo è estremamente importante con queste micro-ottimizzazioni. –

risposta

0

ok, ho dovuto guardare MethodBody.GetILAsByteArray() IL piuttosto che i risultati ILSpy per i delegati per andare a fondo di questa. Utilizzando un delegato generico per avvolgere la mia gestore di messaggi e il cast del tipo di messaggio genera:

0000 : ldarg.0 
0001 : ldfld 
0006 : ldarg.1 
0007 : unbox.any 
000C : callvirt void MessageTest.Message+tMessageHandler`1[MessageTest.VelocityUpdateMessage].Invoke(‌​MessageTest.VelocityUpdateMessage) 
0011 : ret 

in cui il delegato involucro con il cast esplicito genera:

0000 : ldarg.0 
0001 : ldarg.1 
0002 : castclass 
0007 : call void Message.Component.HandleVelocityUpdate(MessageTest.VelocityUpdateMessage) 
000C : ret 

Quindi sì, c'è un overhead minimo usare i farmaci generici in questo modo.

3

La riga sottostante crea una nuova istanza di un tipo anonimo ogni volta che viene chiamata. Potrebbe la causa della tua differenza di prestazioni?

AddHandler(typeof(T), e => { handler((T)e); }); 
+0

La riga non contiene un 'new {...}'. Dov'è il tipo anonimo? – dtb

+0

Per chiarire, sto osservando la differenza di prestazioni quando si chiamano i delegati (invio dei messaggi al gestore), non sto definendo le chiamate AddHandler poiché sono codice di installazione. Come dice Christopher, avrò un nuovo metodo scritto per ogni tipo, ma il mio codice di test chiama semplicemente lo stesso gestore più e più volte in un ciclo stretto, quindi l'hit per la generazione del codice dovrebbe essere solo un piccolo blip. Forse è più di quello che penso sia. Vedo un tempo trascorso di ~ 70 ms per 100k chiamate da # 1 a ~ 110 ms per # 2. Non è un ordine di grandezza come chiamare Delegate.DynamicInvoke. –

+0

Non è un tipo anonimo, almeno non nella definizione formale del linguaggio C# di uno. C'è una classe nascosta che fa funzionare l'espressione lambda, anche l'espressione * new * è nascosta. L'OP fa però l'osservazione opposta. –

Problemi correlati