gpt4 book ai didi

c# - 为什么在 Expression.Call 上构建的编译 lambda 比应该做同样事情的委托(delegate)稍慢?

转载 作者:行者123 更新时间:2023-11-30 15:18:18 28 4
gpt4 key购买 nike

为什么在 Expression.Call 上构建的编译 lambda 比应该做同样事情的委托(delegate)稍慢?以及如何避免呢?

解释 BenchmarkDotNet 结果。我们正在比较 CallBuildedRealCallLambda;其他两个 CallBuilded 和 CallLambdaConst 是 CallLambda 的“子形式”并且显示相同的数字。但是与 CallBuildedReal 的区别是显着的。

//[Config(typeof(Config))]
[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
[ClrJob , CoreJob]
[HtmlExporter, MarkdownExporter]
[MemoryDiagnoser /*, InliningDiagnoser*/]
public class BenchmarkCallSimple
{
static Func<StringBuilder, int, int, bool> callLambda;
static Func<StringBuilder, int, int, bool> callLambdaConst;
static Func<StringBuilder, int, int, bool> callBuilded;
static Func<StringBuilder, int, int, bool> callBuildedReal;
private static bool Append<T>(StringBuilder sb, T i1, T i2, Func<T, T, T> operation)
{
sb.Append(operation(i1, i2));
return true;
}

private static Func<StringBuilder, T, T, bool> BuildCallMethod<T>(Func<T, T, T> operation)
{
return (sb, i1, i2)=> { sb.Append(operation(i1, i2)); return true; };
}

private static int AddMethod(int a, int b)
{
return a + b;
}

static BenchmarkCallSimple()
{

var x = Expression.Parameter(typeof(int));
var y = Expression.Parameter(typeof(int));
var additionExpr = Expression.Add(x, y);

callLambdaConst = BuildCallMethod<int>(AddMethod);
callLambda = BuildCallMethod<int>((a, b) => a + b);

var operationDelegate = Expression.Lambda<Func<int, int, int>>(additionExpr, x, y).Compile();
callBuilded = BuildCallMethod(operationDelegate);

var operationExpressionConst = Expression.Constant(operationDelegate, operationDelegate.GetType());

var sb1 = Expression.Parameter(typeof(StringBuilder), "sb");
var i1 = Expression.Parameter(typeof(int), "i1");
var i2 = Expression.Parameter(typeof(int), "i2");
var appendMethodInfo = typeof(BenchmarkCallSimple).GetTypeInfo().GetDeclaredMethod(nameof(BenchmarkCallSimple.Append));
var appendMethodInfoGeneric = appendMethodInfo.MakeGenericMethod(typeof(int));
var appendCallExpression = Expression.Call(appendMethodInfoGeneric,
new Expression[] { sb1, i1, i2, operationExpressionConst }
);
var appendLambda = Expression.Lambda(appendCallExpression, new[] { sb1, i1, i2 });
callBuildedReal = (Func<StringBuilder, int, int, bool>)(appendLambda.Compile());
}

[Benchmark]
public string CallBuildedReal()
{
StringBuilder sb = new StringBuilder();
var b = callBuildedReal(sb, 1, 2);
return sb.ToString();
}

[Benchmark]
public string CallBuilded()
{
StringBuilder sb = new StringBuilder();
var b = callBuilded(sb, 1, 2);
return sb.ToString();
}

[Benchmark]
public string CallLambda()
{
StringBuilder sb = new StringBuilder();
var b = callLambda(sb, 1, 2);
return sb.ToString();
}

[Benchmark]
public string CallLambdaConst()
{
StringBuilder sb = new StringBuilder();
var b = callLambdaConst(sb, 1, 2);
return sb.ToString();
}
}

结果:

BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
Clr : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
Core : .NET Core 4.6.25009.03, 64bit RyuJIT


Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated |
---------------- |----- |-------- |---------:|---------:|---------:|---------:|---------:|---------:|-----:|-------:|----------:|
CallBuildedReal | Clr | Clr | 137.8 ns | 2.903 ns | 4.255 ns | 133.6 ns | 149.6 ns | 135.6 ns | 7 | 0.0580 | 192 B |
CallBuilded | Clr | Clr | 122.7 ns | 2.068 ns | 1.934 ns | 118.5 ns | 126.2 ns | 122.6 ns | 6 | 0.0576 | 192 B |
CallLambda | Clr | Clr | 119.8 ns | 1.342 ns | 1.255 ns | 117.9 ns | 121.7 ns | 119.6 ns | 5 | 0.0576 | 192 B |
CallLambdaConst | Clr | Clr | 121.7 ns | 1.347 ns | 1.194 ns | 120.1 ns | 124.5 ns | 121.6 ns | 6 | 0.0571 | 192 B |
CallBuildedReal | Core | Core | 114.8 ns | 2.263 ns | 2.117 ns | 112.7 ns | 118.8 ns | 113.7 ns | 3 | 0.0594 | 191 B |
CallBuilded | Core | Core | 109.0 ns | 1.701 ns | 1.591 ns | 106.5 ns | 112.2 ns | 108.8 ns | 2 | 0.0599 | 191 B |
CallLambda | Core | Core | 107.0 ns | 1.181 ns | 1.105 ns | 105.7 ns | 109.4 ns | 106.8 ns | 1 | 0.0593 | 191 B |
CallLambdaConst | Core | Core | 117.3 ns | 2.706 ns | 3.704 ns | 113.4 ns | 127.8 ns | 116.0 ns | 4 | 0.0592 | 191 B |

基准代码:

注意 1:有类似的 SO 线程“Performance of expression trees”,其中构建表达式在基准测试中显示最佳结果。

注意2:我应该接近回答什么时候我会得到编译表达式的IL代码,所以我正在尝试学习如何获取编译表达式的IL代码(linqpad?,ilasm集成到VS?,动态汇编?),但是如果您知道可以从 VS 执行此操作的简单插件 - 它会对我有很大帮助。

注3:这行不通

    var assemblyBuilder = System.AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("testLambda"),System.Reflection.Emit.AssemblyBuilderAccess.Save);
var modelBuilder = assemblyBuilder.DefineDynamicModule("testLambda_module", "testLambda.dll");
var typeBuilder = modelBuilder.DefineType("testLambda_type");
var method = typeBuilder.DefineMethod("testLambda_method", MethodAttributes.Public | MethodAttributes.Static, typeof(bool),
new[] { typeof(StringBuilder), typeof(int), typeof(int), typeof(bool) });
appendLambda.CompileToMethod(method);
typeBuilder.CreateType();
assemblyBuilder.Save("testLambda.dll");

由于 System.TypeInitializationException: "InvalidOperationException: CompileToMethod 无法编译常量 'System.Func3[System.Int32,System.Int32,System.Int32]' 因为它是非- 琐碎的值,例如事件对象。相反,创建一个可以构造此值的表达式树。”
这意味着
appendLambda` 包含一个参数类型 is Func,它不是原始类型,并且 CompileToMethod 有一个限制,只能使用原始类型。

最佳答案

由于 reasons,编译后的表达式可能会变慢:

TL;DR;

The question is, why is the compiled delegate way slower than a manually-written delegate? Expression.Compile creates a DynamicMethod and associates it with an anonymous assembly to run it in a sand-boxed environment. This makes it safe for a dynamic method to be emitted and executed by partially trusted code but adds some run-time overhead.

有类似 FastExpressionCompiler 的工具这有助于缓解问题(免责声明:我是作者)

更新:查看已编译委托(delegate)的IL

  1. 可以将已编译的委托(delegate) IL 作为字节数组获取:

    var hello = "Hello";
    Expression<Func<string>> getGreetingExpr = () => hello + " me";

    var getGreeting = getGreetingExpr.Compile();

    var methodBody = getGreeting.Method.GetMethodBody();

    var ilBytes = methodBody.GetILAsByteArray();
  2. 您需要一种方法来解析/读取数组并将其转换为 IL 指令和参数。

可惜,但我没有找到允许我这样做的工具或强大的 NuGet 包:-(

这是相关的SO question .

最接近的工具可能是this .

关于c# - 为什么在 Expression.Call 上构建的编译 lambda 比应该做同样事情的委托(delegate)稍慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44239127/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com