c# performance lambda expression virtual-method

c# - Rendimiento de Expression.Compile vs Lambda, llamadas directas vs virtuales



performance virtual-method (3)

Tengo curiosidad por saber cuál es el rendimiento de Expression.Compile comparación con la expresión lambda en el código y en comparación con el uso directo de métodos, y también las llamadas a métodos directos y las llamadas a métodos virtuales (pseudo código):

var foo = new Foo(); var iFoo = (IFoo)foo; foo.Bar(); iFoo.Bar(); (() => foo.Bar())(); (() => iFoo.Bar())(); Expression.Compile(foo, Foo.Bar)(); Expression.Compile(iFoo, IFoo.Bar)(); Expression.CompileToMethod(foo, Foo.Bar); Expression.CompileToMethod(iFoo, IFoo.Bar); MethodInfo.Invoke(foo, Foo.Bar); MethodInfo.Invoke(iFoo, IFoo.Bar);


Consejo: en el modo de liberación no se realiza ninguna llamada en el caso de "Llamada directa". La CPU va de 00B531BC ( mov eax ... ) a 00B531C8 ( jl 00B531BC ) solamente.

for (int i = 0; i < iterationCount; i++) 00B531BA xor edx,edx foo.Bar(); 00B531BC mov eax,dword ptr [ebx+4] // actual loop begin 00B531BF cmp byte ptr [eax],al for (int i = 0; i < iterationCount; i++) 00B531C1 inc edx 00B531C2 cmp edx,0BEBC200h // 0BEBC200h = 200000000 00B531C8 jl 00B531BC // loop begin address


No encontré ninguna respuesta, así que aquí está la prueba de rendimiento:

using System; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; namespace ExpressionTest { public interface IFoo { int Bar(); } public sealed class FooImpl : IFoo { public int Bar() { return 0; } } class Program { static void Main(string[] args) { var foo = new FooImpl(); var iFoo = (IFoo)foo; Func<int> directLambda = () => foo.Bar(); Func<int> virtualLambda = () => iFoo.Bar(); var compiledDirectCall = CompileBar(foo, asInterfaceCall: false); var compiledVirtualCall = CompileBar(foo, asInterfaceCall: true); var compiledArgDirectCall = CompileBar<FooImpl>(); var compiledArgVirtualCall = CompileBar<IFoo>(); var barMethodInfo = typeof(FooImpl).GetMethod(nameof(FooImpl.Bar)); var iBarMethodInfo = typeof(IFoo).GetMethod(nameof(IFoo.Bar)); var compiledToModuleDirect = CompileToModule<FooImpl>(); var compiledToModuleVirtual = CompileToModule<IFoo>(); var iterationCount = 200000000; Console.WriteLine($"Iteration count: {iterationCount:N0}"); var sw = Stopwatch.StartNew(); for (int i = 0; i < iterationCount; i++) compiledVirtualCall(); var elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual (Func<int>)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledDirectCall(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func<int>)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledArgVirtualCall(iFoo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual (Func<IFoo, int>)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledArgDirectCall(foo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func<FooImpl, int>)Expression.Compile(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledToModuleVirtual(iFoo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual (Func<IFoo, int>)Expression.CompileToMethod(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) compiledToModuleDirect(foo); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct (Func<FooImpl, int>)Expression.CompileToMethod(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) virtualLambda(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual () => IFoo.Bar(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) directLambda(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct () => FooImpl.Bar(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) iFoo.Bar(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual IFoo.Bar(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) foo.Bar(); elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct Foo.Bar(): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) { int result = (int)iBarMethodInfo.Invoke(iFoo, null); } elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Virtual MethodInfo.Invoke(FooImpl, Bar): {elapsedMs} ms"); sw.Restart(); for (int i = 0; i < iterationCount; i++) { int result = (int)barMethodInfo.Invoke(foo, null); } elapsedMs = sw.ElapsedMilliseconds; Console.WriteLine($"Direct MethodInfo.Invoke(IFoo, Bar): {elapsedMs} ms"); } static Func<int> CompileBar(IFoo foo, bool asInterfaceCall) { var fooType = asInterfaceCall ? typeof(IFoo) : foo.GetType(); var methodInfo = fooType.GetMethod(nameof(IFoo.Bar)); var instance = Expression.Constant(foo, fooType); var call = Expression.Call(instance, methodInfo); var lambda = Expression.Lambda(call); var compiledFunction = (Func<int>)lambda.Compile(); return compiledFunction; } static Func<TInput, int> CompileBar<TInput>() { var fooType = typeof(TInput); var methodInfo = fooType.GetMethod(nameof(IFoo.Bar)); var instance = Expression.Parameter(fooType, "foo"); var call = Expression.Call(instance, methodInfo); var lambda = Expression.Lambda(call, instance); var compiledFunction = (Func<TInput, int>)lambda.Compile(); return compiledFunction; } static Func<TInput, int> CompileToModule<TInput>() { var fooType = typeof(TInput); var methodInfo = fooType.GetMethod(nameof(IFoo.Bar)); var instance = Expression.Parameter(fooType, "foo"); var call = Expression.Call(instance, methodInfo); var lambda = Expression.Lambda(call, instance); var asmName = new AssemblyName(fooType.Name); var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); var moduleBuilder = asmBuilder.DefineDynamicModule(fooType.Name); var typeBuilder = moduleBuilder.DefineType(fooType.Name, TypeAttributes.Public); var methodBuilder = typeBuilder.DefineMethod(nameof(IFoo.Bar), MethodAttributes.Static, typeof(int), new[] { fooType }); Expression.Lambda<Action>(lambda).CompileToMethod(methodBuilder); var createdType = typeBuilder.CreateType(); var mi = createdType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)[1]; var func = Delegate.CreateDelegate(typeof(Func<TInput, int>), mi); return (Func<TInput, int>)func; } } }

En mi computadora portátil (modo de lanzamiento, 64 bits, .NET 4.5.2) produce:

Iteration count: 200,000,000 Virtual MethodInfo.Invoke(FooImpl, Bar): 61811 ms Direct MethodInfo.Invoke(IFoo, Bar): 37078 ms Virtual (Func<int>)Expression.Compile(): 2894 ms Direct (Func<int>)Expression.Compile(): 2242 ms Virtual (Func<IFoo, int>)Expression.Compile(): 2319 ms Direct (Func<FooImpl, int>)Expression.Compile(): 2051 ms Virtual (Func<IFoo, int>)Expression.CompileToMethod(): 996 ms Direct (Func<FooImpl, int>)Expression.CompileToMethod(): 679 ms Virtual () => IFoo.Bar(): 796 ms Direct () => FooImpl.Bar(): 469 ms Virtual IFoo.Bar(): 531 ms Direct Foo.Bar(): 68 ms

Espero que esto ayude.


Podemos dividir una pregunta en 2 casos:

  • ¿Cómo funciona .NET con el método que se llama a sí mismo (pregunta de infraestructura)?
  • ¿Cómo los optimizadores ayudan al método de llamada?

ExpressionTest.exe en modo de lanzamiento con optimización (configuración de lanzamiento predeterminada) .NET 4.5.2:

Compiled Virtual Call: 4625 ms Compiled Direct Call: 3361 ms Lambda Virtual Call: 1096 ms Lambda Direct Call: 576 ms Virtual Call: 649 ms Direct Call: 144 ms

Vemos que "Direct Call" en 4.5 veces más rápido que "Virtual Call". Pero como vemos arriba, no hay ninguna llamada. El método de barra estaba en línea.

ExpressionTest.exe en modo de lanzamiento sin optimización .NET 4.5.2:

Compiled Virtual Call: 5394 ms Compiled Direct Call: 4666 ms Lambda Virtual Call: 1800 ms Lambda Direct Call: 1683 ms Virtual Call: 1154 ms Direct Call: 1112 ms

Por lo tanto, "Direct Call" es aproximadamente un 3-4% más rápido que "Virtual Call".

Pregunta similar: rendimiento de la llamada virtual "directa" frente a la llamada de interfaz en C #