Rendimiento de la expresión compilada para delegar

Estoy generando un árbol de expresión que asigna propiedades de un objeto de origen a un objeto de destino, que luego se compila en unFunc<TSource, TDestination, TDestination> y ejecutado.

Esta es la vista de depuración de la @ resultanLambdaExpression:

.Lambda #Lambda1<System.Func`3[MemberMapper.Benchmarks.Program+ComplexSourceType,MemberMapper.Benchmarks.Program+ComplexDestinationType,MemberMapper.Benchmarks.Program+ComplexDestinationType]>(
    MemberMapper.Benchmarks.Program+ComplexSourceType $right,
    MemberMapper.Benchmarks.Program+ComplexDestinationType $left) {
    .Block(
        MemberMapper.Benchmarks.Program+NestedSourceType $Complex$955332131,
        MemberMapper.Benchmarks.Program+NestedDestinationType $Complex$2105709326) {
        $left.ID = $right.ID;
        $Complex$955332131 = $right.Complex;
        $Complex$2105709326 = .New MemberMapper.Benchmarks.Program+NestedDestinationType();
        $Complex$2105709326.ID = $Complex$955332131.ID;
        $Complex$2105709326.Name = $Complex$955332131.Name;
        $left.Complex = $Complex$2105709326;
        $left
    }
}

Limitado sería:

(left, right) =>
{
    left.ID = right.ID;
    var complexSource = right.Complex;
    var complexDestination = new NestedDestinationType();
    complexDestination.ID = complexSource.ID;
    complexDestination.Name = complexSource.Name;
    left.Complex = complexDestination;
    return left;
}

Ese es el código que asigna las propiedades en estos tipos:

public class NestedSourceType
{
  public int ID { get; set; }
  public string Name { get; set; }
}

public class ComplexSourceType
{
  public int ID { get; set; }
  public NestedSourceType Complex { get; set; }
}

public class NestedDestinationType
{
  public int ID { get; set; }
  public string Name { get; set; }
}

public class ComplexDestinationType
{
  public int ID { get; set; }
  public NestedDestinationType Complex { get; set; }
}

El código manual para hacer esto es:

var destination = new ComplexDestinationType
{
  ID = source.ID,
  Complex = new NestedDestinationType
  {
    ID = source.Complex.ID,
    Name = source.Complex.Name
  }
};

l problema es que cuando compilo elLambdaExpression y comparar el @ resultandelegate es aproximadamente 10 veces más lento que la versión manual. No tengo idea de por qué es eso. Y toda la idea sobre esto es el máximo rendimiento sin el tedio de la asignación manual.

Cuando tomo el código de Bart de Smet de suentrada en el blo sobre este tema y comparando la versión manual del cálculo de números primos versus el árbol de expresión compilado, son completamente idénticos en rendimiento.

Qué puede causar esta gran diferencia cuando la vista de depuración deLambdaExpression se parece a lo que esperarías

EDITA

egún lo solicitado, agregué el punto de referencia que utilicé:

public static ComplexDestinationType Foo;

static void Benchmark()
{

  var mapper = new DefaultMemberMapper();

  var map = mapper.CreateMap(typeof(ComplexSourceType),
                             typeof(ComplexDestinationType)).FinalizeMap();

  var source = new ComplexSourceType
  {
    ID = 5,
    Complex = new NestedSourceType
    {
      ID = 10,
      Name = "test"
    }
  };

  var sw = Stopwatch.StartNew();

  for (int i = 0; i < 1000000; i++)
  {
    Foo = new ComplexDestinationType
    {
      ID = source.ID + i,
      Complex = new NestedDestinationType
      {
        ID = source.Complex.ID + i,
        Name = source.Complex.Name
      }
    };
  }

  sw.Stop();

  Console.WriteLine(sw.Elapsed);

  sw.Restart();

  for (int i = 0; i < 1000000; i++)
  {
    Foo = mapper.Map<ComplexSourceType, ComplexDestinationType>(source);
  }

  sw.Stop();

  Console.WriteLine(sw.Elapsed);

  var func = (Func<ComplexSourceType, ComplexDestinationType, ComplexDestinationType>)
             map.MappingFunction;

  var destination = new ComplexDestinationType();

  sw.Restart();

  for (int i = 0; i < 1000000; i++)
  {
    Foo = func(source, new ComplexDestinationType());
  }

  sw.Stop();

  Console.WriteLine(sw.Elapsed);
}

El segundo es comprensiblemente más lento que hacerlo manualmente, ya que implica una búsqueda en el diccionario y algunas instancias de objetos, pero el tercero debe ser tan rápido como el delegado sin procesar que se invoca y el lanzamiento desdeDelegate aFunc sucede fuera del circuito.

Intenté ajustar el código manual en una función también, pero recuerdo que no hizo una diferencia notable. De cualquier manera, una llamada a función no debería agregar un orden de magnitud de sobrecarga.

También hago el punto de referencia dos veces para asegurarme de que el JIT no interfiera.

EDITA

Puede obtener el código para este proyecto aquí:

https: //github.com/JulianR/MemberMapper

Usé la extensión del depurador Sons-of-Strike como se describe en esa publicación de blog de Bart de Smet para volcar el IL generado del método dinámico:

IL_0000: ldarg.2 
IL_0001: ldarg.1 
IL_0002: callvirt 6000003 ComplexSourceType.get_ID()
IL_0007: callvirt 6000004 ComplexDestinationType.set_ID(Int32)
IL_000c: ldarg.1 
IL_000d: callvirt 6000005 ComplexSourceType.get_Complex()
IL_0012: brfalse IL_0043
IL_0017: ldarg.1 
IL_0018: callvirt 6000006 ComplexSourceType.get_Complex()
IL_001d: stloc.0 
IL_001e: newobj 6000007 NestedDestinationType..ctor()
IL_0023: stloc.1 
IL_0024: ldloc.1 
IL_0025: ldloc.0 
IL_0026: callvirt 6000008 NestedSourceType.get_ID()
IL_002b: callvirt 6000009 NestedDestinationType.set_ID(Int32)
IL_0030: ldloc.1 
IL_0031: ldloc.0 
IL_0032: callvirt 600000a NestedSourceType.get_Name()
IL_0037: callvirt 600000b NestedDestinationType.set_Name(System.String)
IL_003c: ldarg.2 
IL_003d: ldloc.1 
IL_003e: callvirt 600000c ComplexDestinationType.set_Complex(NestedDestinationType)
IL_0043: ldarg.2 
IL_0044: ret 

No soy un experto en IL, pero esto parece bastante directo y exactamente lo que esperarías, ¿no? Entonces, ¿por qué es tan lento? Sin operaciones de boxeo extrañas, sin instancias ocultas, nada. No es exactamente lo mismo que el árbol de expresión anterior, ya que también hay unanull comprobar enright.Complex ahora

Este es el código para la versión manual (obtenida a través de Reflector):

L_0000: ldarg.1 
L_0001: ldarg.0 
L_0002: callvirt instance int32 ComplexSourceType::get_ID()
L_0007: callvirt instance void ComplexDestinationType::set_ID(int32)
L_000c: ldarg.0 
L_000d: callvirt instance class NestedSourceType ComplexSourceType::get_Complex()
L_0012: brfalse.s L_0040
L_0014: ldarg.0 
L_0015: callvirt instance class NestedSourceType ComplexSourceType::get_Complex()
L_001a: stloc.0 
L_001b: newobj instance void NestedDestinationType::.ctor()
L_0020: stloc.1 
L_0021: ldloc.1 
L_0022: ldloc.0 
L_0023: callvirt instance int32 NestedSourceType::get_ID()
L_0028: callvirt instance void NestedDestinationType::set_ID(int32)
L_002d: ldloc.1 
L_002e: ldloc.0 
L_002f: callvirt instance string NestedSourceType::get_Name()
L_0034: callvirt instance void NestedDestinationType::set_Name(string)
L_0039: ldarg.1 
L_003a: ldloc.1 
L_003b: callvirt instance void ComplexDestinationType::set_Complex(class NestedDestinationType)
L_0040: ldarg.1 
L_0041: ret 

Parece idéntico a mí ..

EDITA

Seguí el enlace en la respuesta de Michael B sobre este tema. ¡Intenté implementar el truco en la respuesta aceptada y funcionó! Si desea un resumen del truco: crea un ensamblaje dinámico y compila el árbol de expresión en un método estático en ese ensamblaje y, por alguna razón, es 10 veces más rápido. Una desventaja de esto es que mis clases de referencia eran internas (en realidad, las clases públicas anidadas en una interna) y arrojó una excepción cuando intenté acceder a ellas porque no eran accesibles. No parece haber una solución alternativa para eso, pero simplemente puedo detectar si los tipos a los que se hace referencia son internos o no y decidir qué enfoque de compilación usar.

o que todavía me molesta es por qué ese método de números primose idéntico en rendimiento al árbol de expresión compilado.

Y nuevamente, doy la bienvenida a cualquiera para que ejecute el código en ese repositorio de GitHub para confirmar mis medidas y asegurarse de que no estoy loco

Respuestas a la pregunta(5)

Su respuesta a la pregunta