.NET JIT编译代码缓存在哪里?

49

一个 .NET 程序首先被编译成 MSIL 代码。当它被执行时,JIT 编译器将它编译成本机机器码。

我想知道:

这些 JIT 编译的机器码存储在哪里?它只存储在进程的地址空间中吗?但由于程序的第二次启动比第一次快得多,我认为这些本机代码必须被存储在某个地方的磁盘上,即使执行已经完成。但是在哪里呢?

6个回答

77

内存。它可以被缓存,这是ngen.exe的工作。它生成程序集的.ni.dll版本,包含机器代码并存储在全局程序集缓存中。此后自动加载,跳过JIT步骤。

但这与为什么您的程序第二次启动更快有很少关系。第一次您有所谓的“冷启动”。这完全被花费在硬盘上查找DLLs的时间所主导。第二次您有所谓的“热启动”,DLL已经在文件系统缓存中可用。

磁盘很慢。一个SSD是一个显而易见的解决方案。

值得注意的是:这不是仅适用于托管代码的问题。具有许多DLLs的大型非托管程序也会出现此问题。两个经典示例,几乎所有开发机器上都有的是Microsoft Office和Acrobat Reader。它们有一个技巧。安装时,它们将一个“优化器”放在Run注册表键或Startup文件夹中。这些优化器所做的只是加载主程序使用的所有DLL,然后退出。这将预热文件系统缓存,当用户随后使用该程序时,它将快速启动,因为它的热启动很快。

就个人而言,我发现这非常令人恼火。因为它们真正做的是减慢我可能想要在登录后启动的其他程序。这很少是Office或Acrobat。我会刻意删除这些优化器,如果必要的话反复删除当可恶的更新把它放回去时。

您也可以使用这个技巧,但请负责任地使用。


17
Vista和Windows 7都集成了这个DLL缓存技巧,因此您不再需要制作"应用程序加速器"。 Superfetch记录您最常使用的应用程序,并在启动后读取这些文件。它比加速器应用程序更好,因为它关注当前内存使用情况和实际应用程序使用情况。 - Zan Lynx
1
@Hans:你说过“它会生成一个包含机器码的程序集的 .ni.dll 版本,并存储在 GAC 中”。我有些疑问.. GAC 只用于存储公共程序集,对吗?它是否存储我正在运行的 exeNative Image - Sreekumar P
是的,这就是答案所说的。它被隐藏了起来,但你可以通过启动命令提示符来看到它。输入“dir c:\windows\assembly”以列出GAC中的目录。 - Hans Passant

11

正如其他人指出的那样,在你的情况下,代码是按进程基础进行 JIT 编译的,而且不会被缓存 - 你在第二次加载时看到的加速是操作系统磁盘缓存(即内存中的)程序集所致。

然而,在桌面/服务器版本的 .NET Framework 中,除了操作系统磁盘缓存之外,没有缓存 JIT 编译后的机器代码;但在 .NET Framework 的另一个版本中却有缓存。

有趣的是,在 .NET Compact Framework(用于 Windows Phone 7 的 NETCF 版本)中发生了什么。最近的进展看到一些 JIT 后的框架代码在进程之间共享,其中 JIT 编译后的代码确实被缓存。这主要是为了在像移动电话这样的受限设备上提高性能(负载时间和内存使用情况)。

因此,回答这个问题,CLR 桌面/服务器版本中没有直接缓存 JIT 编译后的代码,但在最新版本的紧凑框架即 NETCF 中将有缓存。

参考: We Believe in Sharing

链接


7
JIT编译的机器代码会根据每个方法在内存中缓存,每次执行该方法时第一次执行。我认为它永远不会被缓存到磁盘中。
您可能会发现,第二次加载进程的速度更快,因为Windows在第一次运行时已经缓存了您的进程使用的文件(dll,资源等等)。在第二次运行时,无需访问磁盘,在第一次运行时可能已经完成。
您可以通过运行 NGen.exe来实际预编译您架构的机器代码,并比较第一次和第二次运行的性能来确认这一点。我的打赌是由于操作系统的缓存,第二次运行仍然会更快。

你知道Windows将进程使用的这些文件(dll、资源等)缓存到哪里了吗? - Sreekumar P
@Sreekumar:在RAM中,与现今几乎所有其他操作系统一样。 - SamB

2
简而言之,每次程序调用时,IL都会进行JIT编译,并保存在进程地址空间的代码页中。请参阅Richter的第1章,了解.NET执行模型的详细介绍。

1

我相信即时编译的代码永远不会存储或交换出内存。您在第二次执行汇编时感受到的性能提升是由于依赖程序集已经存在于内存或磁盘缓存中。


只有内核驱动程序才能防止代码被交换出去。 JIT编译的代码可以像应用程序内存中的任何其他页面一样被分页。 - Ben Voigt

1
是的,即使没有MSIL版本,NGEN.EXE也会将.NET可执行文件的JIT编译版本放置在GAC中。我试过了,但没有成功。 我认为,除非原始的MSIL版本也在GAC中并从那里加载,否则GAC中的JIT版本不会被使用。 我还相信,在运行时JIT编译(而不是 NGEN)永远不会被缓存;它们只占用进程内存。
我通过阅读MS文档和各种实验得出这个结论。我希望那些“知道”的人可以确认或反驳我的说法。

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