烘焙方法是什么意思?

7
我在Stack Overflow上读到了一篇关于微型ORM的文章。
作者展示了以下堆栈跟踪:
System.Reflection.Emit.DynamicMethod.CreateDelegate
System.Data.Linq.SqlClient.ObjectReaderCompiler.Compile
System.Data.Linq.SqlClient.SqlProvider.GetReaderFactory
System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Compile
System.Data.Linq.CommonDataServices+DeferredSourceFactory`1.ExecuteKeyQuery
System.Data.Linq.CommonDataServices+DeferredSourceFactory`1.Execute
System.Linq.Enumerable.SingleOrDefault
System.Data.Linq.EntityRef`1.get_Entity

接着说:

在上面的跟踪中,您可以看到'EntityRef'正在制作一个方法,这不是问题,除非它每秒发生100次。

有人能解释一下与“制作方法”相关的堆栈跟踪以及为什么它会成为性能问题吗?

3个回答

10

当您执行以下操作:

int[] items = whatever;
IEnumerable<int> query = from item in items where item % 2 == 0 select item;

那么编译器会将其转换为类似于以下内容:

static bool Predicate(int item) { return item % 2 == 0; }
...
IEnumerable<int> query = Enumerable.Where<int>(items, new Func<int, bool> (Predicate));
也就是说,编译器会为该方法生成IL。当你在运行时构建查询对象时,Where返回的对象保存了对谓词的委托,并在必要时执行谓词。
但是,如果需要的话,可以在运行时构建委托的IL。你需要将谓词主体持久化为一个表达式树。在运行时,表达式树可以动态地编译自己成全新的IL;基本上我们启动了一个非常简化的编译器,知道如何为表达式树生成IL。这样,你就可以在运行时更改谓词的详细信息并重新编译谓词,而无需重新编译整个程序。
该评论的作者使用“烘焙”一词来表示“动态轻量级代码生成”的俚语。

谢谢,你能举个例子或解释一下表达式树需要在运行时重新编译的情况吗?例如,在你的例子中,是否有任何部分需要在运行时重新编译? - Shawn Mclean
1
@Lolcoder:假设您从用户那里获取了查询的详细信息,因为他们输入了他们想要搜索的关键字,或者他们想要执行过滤、排序或其他操作。然后,您可能希望动态构建谓词,而不是让编译器为您构建它。另一个常见的用例是将表达式树转换为不是委托,而是真正的SQL语句,然后将其传输到后端数据库。 - Eric Lippert

1

它指的是在运行时动态创建方法,例如使用表达式树。将表达式树编译为方法并返回编译后方法的委托可以称为“烘焙”该方法。


你怎么从堆栈跟踪中知道的?仅仅是第一行写着 DynamicMethod.CreateDelegate? - Shawn Mclean
1
@Lolcoder 我并不是说这个堆栈跟踪特别是在使用或操作表达式树。我一般地谈论了解“烘焙方法”这个术语的理解。你可以通过发出 IL 代码来实现它;你可以使用表达式树来实现它;也可能有其他的方法来实现它。或者你是在一般地问如何知道堆栈跟踪指示“烘焙”吗?这可以从对 CompileCreateDelegate 的调用中看出。 - phoog

0

这是一种花哨的说法,意思是他们正在使用Reflection.Emit发出动态代码。这是一个臭名昭著的缓慢过程。


@Lolcoder:性能受损是由于使用动态轻量级代码生成,该代码位于Reflection命名空间中。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接