¿Por qué se crea Func <> a partir de la expresión > más lento que Func <> declarado directamente?

c# delegates expression expression-trees func

Pregunta

¿Por qué un Func<> creado a partir de una Expression<Func<>> través de .Compile () es considerablemente más lento que el simple uso de un Func<> declarado directamente?

Acabo de cambiar el uso de un Func<IInterface, object> declarado directamente a uno creado a partir de una Expression<Func<IInterface, object>> en una aplicación en la que estoy trabajando y noté que el rendimiento disminuyó.

Acabo de hacer una pequeña prueba, y el Func<> creado a partir de una Expresión toma "casi" el doble del tiempo de un Func<> declarado directamente.

En mi máquina, Direct Func<> tarda unos 7,5 segundos y la Expression<Func<>> toma unos 12,6 segundos.

Aquí está el código de prueba que usé (corriendo Net 4.0)

// Direct
Func<int, Foo> test1 = x => new Foo(x * 2);

int counter1 = 0;

Stopwatch s1 = new Stopwatch();
s1.Start();
for (int i = 0; i < 300000000; i++)
{
 counter1 += test1(i).Value;
}
s1.Stop();
var result1 = s1.Elapsed;



// Expression . Compile()
Expression<Func<int, Foo>> expression = x => new Foo(x * 2);
Func<int, Foo> test2 = expression.Compile();

int counter2 = 0;

Stopwatch s2 = new Stopwatch();
s2.Start();
for (int i = 0; i < 300000000; i++)
{
 counter2 += test2(i).Value;
}
s2.Stop();
var result2 = s2.Elapsed;



public class Foo
{
 public Foo(int i)
 {
  Value = i;
 }
 public int Value { get; set; }
}

¿Cómo puedo recuperar el rendimiento?

¿Hay algo que pueda hacer para que la Func<> creada a partir de la Expression<Func<>> realice como una declarada directamente?

Respuesta aceptada

Como han mencionado otros, la sobrecarga de llamar a un delegado dinámico está causando su desaceleración. En mi computadora, esa sobrecarga es de aproximadamente 12 ns con mi CPU a 3GHz. La forma de evitar eso es cargar el método desde un ensamblaje compilado, como este:

var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
             new AssemblyName("assembly"), AssemblyBuilderAccess.Run);
var mod = ab.DefineDynamicModule("module");
var tb = mod.DefineType("type", TypeAttributes.Public);
var mb = tb.DefineMethod(
             "test3", MethodAttributes.Public | MethodAttributes.Static);
expression.CompileToMethod(mb);
var t = tb.CreateType();
var test3 = (Func<int, Foo>)Delegate.CreateDelegate(
                typeof(Func<int, Foo>), t.GetMethod("test3"));

int counter3 = 0;
Stopwatch s3 = new Stopwatch();
s3.Start();
for (int i = 0; i < 300000000; i++)
{
    counter3 += test3(i).Value;
}
s3.Stop();
var result3 = s3.Elapsed;

Cuando agrego el código anterior, el result3 siempre es solo una fracción de segundo más alto que el result1 , para una sobrecarga de aproximadamente 1 ns.

Entonces, ¿por qué molestarse con un lambda compilado ( test2 ) cuando puede tener un delegado más rápido ( test3 )? Debido a que la creación del ensamblaje dinámico es mucho más general en general, y solo le ahorra 10-20ns en cada invocación.


Respuesta popular

(Esta no es una respuesta adecuada, pero es material destinado a ayudar a descubrir la respuesta).

Estadísticas recopiladas de Mono 2.6.7 - Debian Lenny - Linux 2.6.26 i686 - núcleo único de 2.80GHz:

      Func: 00:00:23.6062578
Expression: 00:00:23.9766248

Así que en Mono al menos ambos mecanismos parecen generar IL equivalente.

Esta es la IL generada por los gmcs de Mono para el método anónimo:

// method line 6
.method private static  hidebysig
       default class Foo '<Main>m__0' (int32 x)  cil managed
{
    .custom instance void class [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::'.ctor'() =  (01 00 00 00 ) // ....

    // Method begins at RVA 0x2204
    // Code size 9 (0x9)
    .maxstack 8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.2
    IL_0002:  mul
    IL_0003:  newobj instance void class Foo::'.ctor'(int32)
    IL_0008:  ret
} // end of method Default::<Main>m__0

Trabajaré en la extracción del IL generado por el compilador de expresiones.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow