2012-01-21 4 views
6

Estoy construyendo un mapa de envío de mensajes en C# y sobre todo jugando con diferentes enfoques. Tengo curiosidad sobre la diferencia de rendimiento que estoy midiendo, pero no es obvio por qué mirando el IL.¿Por qué el lanzamiento a un tipo genérico es más lento que un lanzamiento explícito en C#?

El mapa mensaje:

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; 

entonces tengo una jerarquía de clases de mensajes, similar a EventArgs en WPF, por ejemplo:

public class Message {} 
public class VelocityUpdateMessage : Message 

y de observación clases con funciones de controlador:

void HandleVelocityUpdate(VelocityUpdateMessage message) { ... } 

Estoy midiendo 2 formas de agregar & controladores de invocación. Estoy completando la llamada de delegado para poder obtener un poco de seguridad de tipo conceptual y ahí radica la diferencia de rendimiento.

Enfoque 1: llamadas de los oyentes

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

Enfoque 2: llamadas de los oyentes

AddHandler<VelocityUpdateMessage>(HandleVelocityUpdate); 

Ambos enfoques construir un delegado MessageHandler que hace un reparto y la misma llamada a un método, pero llamar a los delegados construido usando el enfoque n. ° 2 es un poco más lento a pesar de que el IL generado parece idéntico. ¿Es una sobrecarga de tiempo de ejecución adicional en el envío a un tipo genérico? ¿Es la restricción de tipo? Espero que los delegados JITted sean los mismos una vez que se resuelva el tipo genérico.

Gracias por cualquier información.

+2

¿Cómo estás midiendo? Eso es extremadamente importante con estas micro-optimizaciones. –

Respuesta

0

bien, tuve que mirar a MethodBody.GetILAsByteArray() en lugar de IL ILSpy los resultados de los delegados para llegar al fondo de esto. El uso de un delegado genérico para envolver mi manejador de mensajes y emitir el tipo de mensaje 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 

donde el delegado envoltorio con la conversión explícita genera:

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

Así que sí, hay una sobrecarga mínima del uso de los genéricos De este modo.

3

La siguiente línea crea una nueva instancia de tipo anónimo cada vez que se invoca. ¿Podría ser esa la causa de tu diferencia de rendimiento?

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

La línea no contiene un 'nuevo {...}'. ¿Dónde está el tipo anónimo? – dtb

+0

Para aclarar, veo la diferencia en el rendimiento cuando llamo a los delegados (enviando los mensajes al controlador), no estoy perfilando las llamadas AddHandler ya que son código de configuración. Como dice Christopher, conseguiré un nuevo método para cada tipo, pero mi código de prueba solo llama al mismo controlador una y otra vez en un circuito cerrado, por lo que el acierto de generación de código debería ser solo un pequeño blip. Tal vez es más de lo que creo que es. Veo un tiempo transcurrido de ~ 70ms para 100k llamadas de # 1 vs. ~ 110ms para # 2. No son órdenes de magnitud como llamar a Delegate.DynamicInvoke. –

+0

No es un tipo anónimo, al menos no en la definición formal de lenguaje de C#. Hay una clase oculta que hace que la expresión lambda funcione, la expresión * new * también está oculta. El OP hace la observación opuesta sin embargo. –

Cuestiones relacionadas