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

15

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

我发现,调试配置提供了更好的性能(大约快5倍),因为它们是在应用程序进程的生命周期内首次执行的。例如,在应用程序中打开特定屏幕需要0.3秒的NGENed Debug,0.5秒的JITted Debug,1.5秒的NGENed Release和2.5秒的JITted Release。

我理解JIT编译时间差距是由于JIT编译器对发布版本应用了更加激进的优化。从我所看到的情况来看,调试和发布配置不同取决于传递给C#编译器的/p:DebugType/p:Optimize开关,但即使我使用 /p:Configuration=Release /p:DebugType=full /p:Optimize=false 构建应用程序,也会看到相同的性能差距 - 即与/p:Configuration=Debug相同图像调试选项。

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

注意:由于外部依赖关系,我使用32位框架。在x64上是否应该期望不同的结果?

注意:我也不使用条件编译。因此,编译后的源代码对于两个配置都是相同的。


1
你的发布 NGENed 程序集仍然比调试慢,你确定 JIT 是问题吗?你可以尝试使用分析器...另外,请检查你的代码中是否使用了 #if DEBUG。 - Guillaume
1
你是否在不使用 SGEN 的情况下使用 XmlSerializer?https://dev59.com/-EfRa4cB1Zd3GeqP70kf - Guillaume
我没有使用 #if DEBUG(已编辑问题以反映此内容)。应用程序在发布时不一定会变慢 - 它甚至可能会更快,但我正在测量冷启动时间,而不是吞吐量。我怀疑是动态方法的 JITting,因此我想知道是什么决定了这些方法的优化级别。 - cynic
不,冷启动是指在开机后第一次运行应用程序。主要是查找磁盘上的文件。Ngen 会使情况变得更糟,因为它会使文件数量增加一倍。这就是为什么 Microsoft 建议不要对小型程序集进行 ngen。你正在测量热启动时间。是的,ngen 可以加快速度。没有 jitting 和没有优化 - Hans Passant
尝试在记事本或类似的文本编辑器中打开您的项目文件,并查看是否有其他编译设置的差异是您忽略的(通常是屏幕中隐藏的设置)。 - Seph
显示剩余5条评论
3个回答

2
如果你所说的是真的,你需要加载304个程序集,那么这很可能是你的应用程序运行缓慢的原因。看起来这个数量非常高。
每当CLR调用另一个还未被加载到AppDomain中的程序集时,它就必须从磁盘上加载该程序集。
你可以考虑使用ILMerge来合并一些程序集,这将减少从磁盘加载程序集所需的延迟(你需要承担一个更大的磁盘开销)。但这可能需要一些实验,因为不是所有程序都适合合并(特别是那些使用反射并依赖于程序集文件名永不改变的程序)。此外,这可能会导致生成非常大的程序集。

我会尝试,但我认为这不太可能是JIT调试和NGEN发布配置之间速度差异的原因。我在一台SSD机器上运行测试。 - cynic

1
你是在调试器('F5')下运行还是不带调试器('ctrl+F5')下运行?如果是前者,请确保工具 -> 选项 -> 调试 -> "在模块加载时抑制JIT优化"未被选中。

我通过命令行运行可执行文件来进行测量。所以不是这个问题。 - cynic
嗯,我猜这也不会比DEBUG更慢。 - Mark Sowul

1

好的,这里有几个问题。

据我所知,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,则异常处理代码将使用PDB针对编译的代码提供更有用的堆栈跟踪。Release模式还会在内部触发一些内存改进,因为Debug版本用于附加调试器。

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

至于32位(x86)依赖项,您的应用程序现在将在x86模式下运行。如果该依赖项同时提供了x86和x64版本,并且如果您的应用程序是在“任何CPU”编译模式下编译的,则JIT编译器将自动在两者之间切换。NGEN仅会为当前计算机生成特定版本。因此,如果您进行了NGEN然后分发,它只能在您针对的特定架构上工作。

如果您没有使用条件编译功能,则从调试切换到发布并不重要。但是,在发布模式下,您将看到性能提升。

使用NGEN时,建议您进行广泛测试,以查看其与其他方法相比的优势。它并不总是会带来更好的性能。


我相信对于被ConditionalAttribute标记的方法的调用会在编译成IL代码的阶段被剥离,而不是在JIT编译时。无论如何,在我的情况下,区别并不在于JIT/NGEN,而是Debug/Release。 - cynic
如果配置相同,您是在询问Debug和Release之间的区别吗? - Dominic Zukiewicz
是的。在PE中除了DebuggableAttribute之外还有其他标志吗?最重要的是,动态生成的代码应用了什么级别的优化? - cynic
我不认为有这样的事情?发布版本只是一组预配置选项,以使您的应用程序运行得更加优化。ASP.NET 应用程序的工作方式不同,因为它们是即时生成的。 - Dominic Zukiewicz

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