Expression.Compile 与 Lambda、直接调用与虚拟调用的性能对比
Performance of Expression.Compile vs Lambda, direct vs virtual calls
我很好奇 Expression.Compile 与代码中的 lambda 表达式、直接方法使用以及直接方法调用与虚拟方法调用(伪代码)相比性能如何:
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);
我没有找到任何答案,所以这里是性能测试:
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;
}
}
}
在我的笔记本电脑上(发布模式,64 位,.NET 4.5.2)它产生:
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
希望对您有所帮助。
提示:在发布模式下 "Direct Call" 情况下根本没有调用。
CPU 仅从 00B531BC (mov eax ...) 到 00B531C8 (jl 00B531BC)。
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
我们可以将一个问题拆分为 2 个案例:
- 裸 .NET 如何处理方法调用本身(基础结构问题)?
- 优化器如何辅助方法调用?
ExpressionTest.exe Release 模式 with optimization (default release settings) .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
我们看到 "Direct Call" 比 "Virtual Call" 快 4.5 倍。但正如我们在上面看到的那样,它根本就不是电话。 Bar 方法已内联。
ExpressionTest.exe 发布 模式 无优化 .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
因此,"Direct Call" 比 "Virtual Call" 快 3-4%。
类似问题:
Performance of "direct" virtual call vs. interface call in C#
我在 .NET Core 3.1 上稍微修改了@Serge Semonov 和 运行 的代码 - 似乎 Expression.Compile() 的性能有发生了翻天覆地的变化。我还添加了使用 CSharpScript 从字符串编译 lambda 的代码。请注意,.CompileToMethod 在 .NET Core 中不可用。
Virtual (Func<int>)Expression.Compile(): 908 ms
Direct (Func<int>)Expression.Compile(): 584 ms
Virtual (Func<IFoo, int>)Expression.Compile(): 531 ms
Direct (Func<FooImpl, int>)Expression.Compile(): 426 ms
Virtual (iFooArg) => iFooArg.Bar(): 622 ms
Direct (fooArg) => fooArg.Bar(): 478 ms
Virtual () => IFoo.Bar(): 640 ms
Direct () => FooImpl.Bar(): 477 ms
Virtual IFoo.Bar(): 431 ms
Direct Foo.Bar(): 319 ms
Virtual CSharpScript.EvaluateAsync: 799 ms
Direct CSharpScript.EvaluateAsync: 748 ms
Virtual CSharpScript.EvaluateAsync + Expression.Compile(): 586 ms
Direct CSharpScript.EvaluateAsync + Expression.Compile(): 423 ms
Virtual MethodInfo.Invoke(FooImpl, Bar): 43533 ms
Direct MethodInfo.Invoke(IFoo, Bar): 29012 ms
代码:
#define NET_FW //if you run this on .NET Framework and not .NET Core or .NET (5+)
using System;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
namespace ExpressionTest
{
public interface IFoo
{
int Bar();
}
public sealed class FooImpl : IFoo
{
[MethodImpl(MethodImplOptions.NoInlining)]
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();
Func<FooImpl, int> directArgLambda = fooArg => fooArg.Bar();
Func<IFoo, int> virtualArgLambda = iFooArg => iFooArg.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));
#if NET_FW
var compiledToModuleDirect = CompileToModule<FooImpl>();
var compiledToModuleVirtual = CompileToModule<IFoo>();
#endif
var compiledViaScriptDirect = CompileViaScript<FooImpl>();
var compiledViaScriptVirtual = CompileViaScript<IFoo>();
var compiledViaExprScriptDirect = CompileFromExprFromScript<FooImpl>();
var compiledViaExprScriptVirtual = CompileFromExprFromScript<IFoo>();
var iterationCount = 0;
int round = 0;
start:
if (round == 0)
{
iterationCount = 2000000;
Console.WriteLine($"Burn in");
Console.WriteLine($"Iteration count: {iterationCount:N0}");
goto doWork;
}
if (round == 1)
{
iterationCount = 200000000;
Console.WriteLine($"Iteration count: {iterationCount:N0}");
goto doWork;
}
return;
doWork:
{
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");
#if NET_FW
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");
#endif
sw.Restart();
for (int i = 0; i < iterationCount; i++)
virtualArgLambda(iFoo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Virtual (iFooArg) => iFooArg.Bar(): {elapsedMs} ms");
sw.Restart();
for (int i = 0; i < iterationCount; i++)
directArgLambda(foo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Direct (fooArg) => fooArg.Bar(): {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++)
compiledViaScriptVirtual(iFoo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Virtual CSharpScript.EvaluateAsync: {elapsedMs} ms");
sw.Restart();
for (int i = 0; i < iterationCount; i++)
compiledViaScriptDirect(foo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Direct CSharpScript.EvaluateAsync: {elapsedMs} ms");
sw.Restart();
for (int i = 0; i < iterationCount; i++)
compiledViaExprScriptVirtual(iFoo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Virtual CSharpScript.EvaluateAsync + Expression.Compile(): {elapsedMs} ms");
sw.Restart();
for (int i = 0; i < iterationCount; i++)
compiledViaExprScriptDirect(foo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Direct CSharpScript.EvaluateAsync + Expression.Compile(): {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");
}
round++;
goto start;
}
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;
}
#if NET_FW
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;
}
#endif
static Func<TInput, int> CompileViaScript<TInput>()
{
ScriptOptions scriptOptions = ScriptOptions.Default;
//Add reference to mscorlib
var mscorlib = typeof(System.Object).Assembly;
var systemCore = typeof(System.Func<>).Assembly;
var thisAssembly = typeof(IFoo).Assembly;
scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore, thisAssembly);
var result = CSharpScript.EvaluateAsync<Func<TInput, int>>("it => it.Bar()", options: scriptOptions).Result;
return result;
}
static Func<TInput, int> CompileFromExprFromScript<TInput>()
{
ScriptOptions scriptOptions = ScriptOptions.Default;
//Add reference to mscorlib
var mscorlib = typeof(System.Object).Assembly;
var systemCore = typeof(System.Func<>).Assembly;
var thisAssembly = typeof(IFoo).Assembly;
scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore, thisAssembly);
var result = CSharpScript.EvaluateAsync<Expression<Func<TInput, int>>>("it => it.Bar()", options: scriptOptions).Result;
var compiledFunction = result.Compile();
return compiledFunction;
}
}
}
如何使用CSharpScript:
https://joshvarty.com/2015/10/15/learn-roslyn-now-part-14-intro-to-the-scripting-api/
https://www.strathweb.com/2018/01/easy-way-to-create-a-c-lambda-expression-from-a-string-with-roslyn/
我很好奇 Expression.Compile 与代码中的 lambda 表达式、直接方法使用以及直接方法调用与虚拟方法调用(伪代码)相比性能如何:
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);
我没有找到任何答案,所以这里是性能测试:
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;
}
}
}
在我的笔记本电脑上(发布模式,64 位,.NET 4.5.2)它产生:
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
希望对您有所帮助。
提示:在发布模式下 "Direct Call" 情况下根本没有调用。 CPU 仅从 00B531BC (mov eax ...) 到 00B531C8 (jl 00B531BC)。
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
我们可以将一个问题拆分为 2 个案例:
- 裸 .NET 如何处理方法调用本身(基础结构问题)?
- 优化器如何辅助方法调用?
ExpressionTest.exe Release 模式 with optimization (default release settings) .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
我们看到 "Direct Call" 比 "Virtual Call" 快 4.5 倍。但正如我们在上面看到的那样,它根本就不是电话。 Bar 方法已内联。
ExpressionTest.exe 发布 模式 无优化 .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
因此,"Direct Call" 比 "Virtual Call" 快 3-4%。
类似问题: Performance of "direct" virtual call vs. interface call in C#
我在 .NET Core 3.1 上稍微修改了@Serge Semonov 和 运行 的代码 - 似乎 Expression.Compile() 的性能有发生了翻天覆地的变化。我还添加了使用 CSharpScript 从字符串编译 lambda 的代码。请注意,.CompileToMethod 在 .NET Core 中不可用。
Virtual (Func<int>)Expression.Compile(): 908 ms
Direct (Func<int>)Expression.Compile(): 584 ms
Virtual (Func<IFoo, int>)Expression.Compile(): 531 ms
Direct (Func<FooImpl, int>)Expression.Compile(): 426 ms
Virtual (iFooArg) => iFooArg.Bar(): 622 ms
Direct (fooArg) => fooArg.Bar(): 478 ms
Virtual () => IFoo.Bar(): 640 ms
Direct () => FooImpl.Bar(): 477 ms
Virtual IFoo.Bar(): 431 ms
Direct Foo.Bar(): 319 ms
Virtual CSharpScript.EvaluateAsync: 799 ms
Direct CSharpScript.EvaluateAsync: 748 ms
Virtual CSharpScript.EvaluateAsync + Expression.Compile(): 586 ms
Direct CSharpScript.EvaluateAsync + Expression.Compile(): 423 ms
Virtual MethodInfo.Invoke(FooImpl, Bar): 43533 ms
Direct MethodInfo.Invoke(IFoo, Bar): 29012 ms
代码:
#define NET_FW //if you run this on .NET Framework and not .NET Core or .NET (5+)
using System;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
namespace ExpressionTest
{
public interface IFoo
{
int Bar();
}
public sealed class FooImpl : IFoo
{
[MethodImpl(MethodImplOptions.NoInlining)]
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();
Func<FooImpl, int> directArgLambda = fooArg => fooArg.Bar();
Func<IFoo, int> virtualArgLambda = iFooArg => iFooArg.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));
#if NET_FW
var compiledToModuleDirect = CompileToModule<FooImpl>();
var compiledToModuleVirtual = CompileToModule<IFoo>();
#endif
var compiledViaScriptDirect = CompileViaScript<FooImpl>();
var compiledViaScriptVirtual = CompileViaScript<IFoo>();
var compiledViaExprScriptDirect = CompileFromExprFromScript<FooImpl>();
var compiledViaExprScriptVirtual = CompileFromExprFromScript<IFoo>();
var iterationCount = 0;
int round = 0;
start:
if (round == 0)
{
iterationCount = 2000000;
Console.WriteLine($"Burn in");
Console.WriteLine($"Iteration count: {iterationCount:N0}");
goto doWork;
}
if (round == 1)
{
iterationCount = 200000000;
Console.WriteLine($"Iteration count: {iterationCount:N0}");
goto doWork;
}
return;
doWork:
{
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");
#if NET_FW
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");
#endif
sw.Restart();
for (int i = 0; i < iterationCount; i++)
virtualArgLambda(iFoo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Virtual (iFooArg) => iFooArg.Bar(): {elapsedMs} ms");
sw.Restart();
for (int i = 0; i < iterationCount; i++)
directArgLambda(foo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Direct (fooArg) => fooArg.Bar(): {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++)
compiledViaScriptVirtual(iFoo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Virtual CSharpScript.EvaluateAsync: {elapsedMs} ms");
sw.Restart();
for (int i = 0; i < iterationCount; i++)
compiledViaScriptDirect(foo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Direct CSharpScript.EvaluateAsync: {elapsedMs} ms");
sw.Restart();
for (int i = 0; i < iterationCount; i++)
compiledViaExprScriptVirtual(iFoo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Virtual CSharpScript.EvaluateAsync + Expression.Compile(): {elapsedMs} ms");
sw.Restart();
for (int i = 0; i < iterationCount; i++)
compiledViaExprScriptDirect(foo);
elapsedMs = sw.ElapsedMilliseconds;
Console.WriteLine($"Direct CSharpScript.EvaluateAsync + Expression.Compile(): {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");
}
round++;
goto start;
}
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;
}
#if NET_FW
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;
}
#endif
static Func<TInput, int> CompileViaScript<TInput>()
{
ScriptOptions scriptOptions = ScriptOptions.Default;
//Add reference to mscorlib
var mscorlib = typeof(System.Object).Assembly;
var systemCore = typeof(System.Func<>).Assembly;
var thisAssembly = typeof(IFoo).Assembly;
scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore, thisAssembly);
var result = CSharpScript.EvaluateAsync<Func<TInput, int>>("it => it.Bar()", options: scriptOptions).Result;
return result;
}
static Func<TInput, int> CompileFromExprFromScript<TInput>()
{
ScriptOptions scriptOptions = ScriptOptions.Default;
//Add reference to mscorlib
var mscorlib = typeof(System.Object).Assembly;
var systemCore = typeof(System.Func<>).Assembly;
var thisAssembly = typeof(IFoo).Assembly;
scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore, thisAssembly);
var result = CSharpScript.EvaluateAsync<Expression<Func<TInput, int>>>("it => it.Bar()", options: scriptOptions).Result;
var compiledFunction = result.Compile();
return compiledFunction;
}
}
}
如何使用CSharpScript:
https://joshvarty.com/2015/10/15/learn-roslyn-now-part-14-intro-to-the-scripting-api/
https://www.strathweb.com/2018/01/easy-way-to-create-a-c-lambda-expression-from-a-string-with-roslyn/