.NET JIT编译性能(包括动态方法)如何受C#编译器的映像调试选项的影响?

我正在尝试优化我的应用程序,以便它在启动后立即运行良好。 目前,它的发行版包含304个二进制文件(包括外部依赖项),总计57兆字节。 它是一个主要进行数据库访问的WPF应用程序,没有任何重要的计算。

我发现Debug配置为大多数操作提供了更好的(~5倍增益)时间,因为它们是在应用程序进程的生命周期中第一次执行。 例如,在应用程序中打开特定屏幕,NGENed Debug需要0.3秒,JITted Debug需要0.5秒,NGENed Release需要1.5秒,JITted Release需要2.5秒。

我知道JIT编译时间的差距是由JIT编译器对Release二进制文件应用更积极的优化引起的。 据我所知,Debug和Release配置因传递给C#编译器的/p:DebugType/p:Optimize开关而不同,但即使用/p:Configuration=Release /p:DebugType=full /p:Optimize=false构建应用程序,我也看到相同的性能差距/p:Configuration=Release /p:DebugType=full /p:Optimize=false – 即与/p:Configuration=Debug相同的映像调试选项。

我确认通过查看应用于生成的程序集的DebuggableAttribute应用这些选项。 观察NGEN输出,我看到添加到正在编译的一些程序集的名称中 – NGEN如何区分调试和非调试程序集? 正在测试的操作使用动态代码生成 – 动态代码应用了什么级别的优化?

注意:由于外部依赖性,我使用的是32位框架。 我应该期待x64有不同的结果吗?

注意:我也不使用条件编译。 因此,两种配置的编译源都相同。

如果,如您所说,您要加载304个程序集,那么这可能是您的应用程序运行缓慢的原因。 这似乎是要加载的极大数量的组件。

每当CLR从另一个尚未加载到AppDomain中的程序集中获取代码时,它就必须从磁盘加载它。

您可以考虑使用ILMerge合并其中一些程序集。 这将减少从磁盘加载程序集的延迟(您需要预先设置一个更大的磁盘)。

它可能需要一些实验,因为并非一切都喜欢被合并(特别是那些使用Reflection,并且依赖于程序集文件名永远不会改变)。 它也可能导致非常大的组件。

好的,这里有几个问题。

据我所知,Debug和Release配置因传递给C#编译器的/ p:DebugType和/ p:Optimize开关而不同,但即使用/ p构建应用程序,我也看到相同的性能差距:Configuration = Release / p:DebugType = full / p:Optimize = false – 即与/ p:Configuration = Debug中相同的映像调试选项。

虽然勾选框是相同的,但更改为Release模式也会导致某些内部代码路径被删除,例如Debug.Assert() (在Microsoft内部代码中大量使用)。 因此,这些不会在运行时进行评估,从而导致一些性能提升。 DebugType=full生成一个与编译的代码匹配的PDB,因此本身不会影响性能。 如果部署了PDB,则exception处理代码将使用PDB针对编译的代码提供更有用的堆栈跟踪。 发布模式还在内部触发一些内存改进,因为Debug版本用于附加调试器。

NGEN是一种用于“潜在地”优化应用程序的工具。 它优化了代码,使其专门针对您所使用的计算机。 但它可能有缺点,因为JIT编译器可以更改内存中代码的布局,而NGEN本质上更加静态。

对于32位(x86)依赖项,您的应用程序现在将以x86模式运行。 如果依赖项同时具有x86和x64版本,并且如果您的应用程序在“任何CPU”编译模式下编译,则JIT编译器将在2之间自动切换.NGEN将仅为当前计算机生成特定版本。 因此,如果你做NGEN然后分发,它只适用于你编译的特定架构。

如果您没有使用条件编译function,那么从Debug切换到Release并不重要。 但是你会在Release中看到性能优势。

有了NGEN,我建议你进行广泛的测试,看看它的好处。它并不总能带来更好的性能。

你是在调试器(’F5’)下运行它还是没有调试器(’ctrl + F5’)? 如果是前者,请确保取消选中工具 – >选项 – >调试 – >“抑制模块加载时的JIT优化”