C# 如何证明.NET CLR JIT每次运行只编译每个方法一次?

C# 如何证明.NET CLR JIT每次运行只编译每个方法一次?,c#,.net,clr,windbg,jit,C#,.net,Clr,Windbg,Jit,有人问C#是否每次都进行JIT编译,著名的Jon Skeet的回答是:“不,每个应用程序只编译一次”,只要我们谈论的是没有进行JIT编译的桌面应用程序 我想知道2009年的信息是否仍然正确,我想通过实验和调试,通过在抖动上设置断点,并使用WinDbg命令检查对象和方法,找出答案 到目前为止我的研究 我知道.NET内存布局在实际数据开始(地址a+4)之前会考虑每个对象的头(地址a-4)和方法表(地址a+0)。因此,每个对象都可能有不同的方法表,因此可能有不同的JITted方法 为什么我对这个说法的

有人问C#是否每次都进行JIT编译,著名的Jon Skeet的回答是:“不,每个应用程序只编译一次”,只要我们谈论的是没有进行JIT编译的桌面应用程序

我想知道2009年的信息是否仍然正确,我想通过实验和调试,通过在抖动上设置断点,并使用WinDbg命令检查对象和方法,找出答案

到目前为止我的研究 我知道.NET内存布局在实际数据开始(地址a+4)之前会考虑每个对象的头(地址a-4)和方法表(地址a+0)。因此,每个对象都可能有不同的方法表,因此可能有不同的JITted方法

为什么我对这个说法的正确性有怀疑? 我们有一个关于并行编程的研讨会,培训师的一个说法是,每个线程的每个对象都会对方法进行JIT。这对我来说显然没有意义,我能够编写一个反例应用程序

不幸的是,出现了以下其他主题,我还想为此编写一个演示:

  • 新的.NET框架
  • 应用程序域
  • 代码访问安全
链接答案是在.NET3.5发布时编写的。自那时以来,它没有实质性的改变,即它没有收到.NET 4.0、4.6和4.6的更新

关于应用程序域,我个人的意见是,我可以卸载一个应用程序域,它卸载程序集。如果卸载了一个程序集,它就会消失,IL代码也随之消失。我看不出为被销毁的IL代码保留本机代码有什么好处。因此,我可以想象,创建一个应用程序域并再次加载程序集可能会导致再次JITting该方法

关于代码访问安全性,我不确定JIT编译器是否基于当前权限考虑它,或者它是否在运行时通过反射完成。如果是由JIT编译器完成的,编译后的代码会有所不同,这取决于权限集。

JIT的“设计原则”是编译一个方法(泛型方法的方法实例化)一次,然后重用相同的本机代码。当然,实现是极其复杂的,我将尝试在不影响准确性的情况下简化答案。对于从.NET2.0到最新的.NET4.6的所有运行时版本,答案都是相同的(我不知道.NET1.x,可能是相同的)

运行时探查器回调和ETW事件的记录很差。这两种情况都发生在尝试JIT编译时,但不一定成功。有三种情况会发生这种情况:1-方法无法满足某些安全要求,2-方法验证失败,3-无法分配内存来保存要发出的本机代码。因此,JIT启动回调和事件可能会高估方法实际编译的次数。类似地,JIT完成回调和事件也不准确。很少有人会低估同一方法成功编译的次数。此时值得一提的是,这些报告准确地记录了所有IL方法在一个进程的所有AppDomain中被集体编译的次数

对于appdomain特定的程序集,在每个appdomain中分别编译方法。没有共享(即使有时在技术上可能)。对于appdomain中立程序集,运行时尝试将每个方法编译一次,并与所有appdomain共享本机代码

如何证明.NET CLR JIT每次只编译一次每个方法 跑

在某些情况下(例如使用后台JIT和其他非常微妙的情况),可以编译方法而不执行。所以说每个方法每次运行编译一次是不准确的

有关更多信息,您可以参考CoreCLR JIT源代码(JIT与.NET framework 4.5+中使用的JIT相同,但此答案适用于较旧版本,因为JIT触发机制基本相同)。源代码就是证明

对于每个线程的每个对象,方法都是JIT的


是的,那没有任何意义。编译的范围是appdomains。

我的第一个想法是,如果你对Jon Skeet关于旧问答的回答进行注释,以验证该信息是否仍然有效,那么你添加一个新的问答是毫无用处的……我不认为代码访问安全性会改变IL如何转化为真正的代码,我不认为新的框架改变了关于何时使用JIT的决定(但是引入了一个新的JIT编译器)。但你关于应用程序域的观点似乎提出了一个真正的新方面,但这一点尚未得到解决。我并不清楚为什么要证明这一点。这种说法肯定不是真的(闪避闪电),通用代码会被抖动编译多次。处理任何引用类型参数的一个副本和每个不同值类型参数的附加副本。当然,对于AppDomains,LoaderOptimization适用。有一些探查器回调告诉您抖动在做什么,ICorProfilerCallback::jitcomilationstarted()和ICorProfilerCallback4::rejitcomilationstarted()。除了Hans的注释外,从.NET 4.5开始,托管探查器可以请求运行时重新jitt方法(以便它可以以不同的方式对其进行检测)。还有一种情况是运行时可以使用ETW捕获.net Jit事件()。在配置文件中,将0x8008替换为0x8018以捕获Jit事件。现在您可以看到带有调用堆栈的加载程序、jit和异常数据。现在看看jit何时发生