【问题标题】:How is .NET JIT compilation performance (including dynamic methods) affected by image debug options of C# compiler?.NET JIT 编译性能(包括动态方法)如何受 C# 编译器的图像调试选项影响?
【发布时间】:2012-04-16 08:08:18
【问题描述】:

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

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

我了解 JIT 编译时间的差距是由 JIT 编译器对 Release 二进制文件应用更积极的优化造成的。据我所知,调试和发布配置因传递给 C# 编译器的 /p:DebugType/p:Optimize 开关而异,但即使我使用 /p:Configuration=Release /p:DebugType=full /p:Optimize=false 构建应用程序,我也看到相同的性能差距——也就是说,相同图像调试选项,如/p:Configuration=Debug

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

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

注意:我也不使用条件编译。所以编译后的源码对于两种配置都是一样的。

【问题讨论】:

  • 由于您的 Release NGENed 程序集仍然比 Debug 慢,您确定 JIT 是问题吗?你可以试试探查器……另外,检查你的代码中没有使用#if DEBUG。
  • 您使用的是没有 SGEN 的 XmlSerializer 吗? stackoverflow.com/questions/771727/…
  • 我没有使用#if DEBUG(已编辑问题以反映这一点)。应用程序在发布时不一定较慢 - 它甚至可能更快,但我正在测量冷启动时间,而不是吞吐量。我怀疑动态方法的 JITting,所以我问是什么决定了这些方法的优化级别。
  • 这一切都没有意义。这些是 warm 启动数字,ngen 使冷启动变慢。我想你应该尝试明确地使用 [Debuggable] 属性。
  • @HansPassant - 你是什么意思?我给出的时间测量如下:在应用程序加载并出现欢迎屏幕(“启动板”)后,单击一个磁贴,并计算时间,直到出现请求的屏幕。这对我来说是“冷”启动。 “温暖”是当我单击“返回”(该应用程序是基于导航的)并再次单击同一个图块时(我的代码没有“缓存”屏幕)。 “温暖”的表现在所有配置中都令人满意。

标签: c# .net clr jit ngen


【解决方案1】:

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

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

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

它可能需要一些实验,因为并非所有东西都喜欢被合并(尤其是那些使用反射,并且依赖于程序集文件名永远不会改变的东西)。它也可能导致非常大的程序集。

【讨论】:

  • 我会尝试,但我认为这不太可能导致 JITted Debug 和 NGENed Release 配置之间的速度差异。我在 SSD 机器上运行测试。
【解决方案2】:

好的,这里有几个问题。

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

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

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

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

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

对于 NGEN,我建议您进行广泛的测试,以了解与 2 相比的优势。它并不总能带来更好的性能。

【讨论】:

  • 我确实相信对ConditionalAttributed 方法的调用在编译到IL 的阶段被剥离,而不是JIT。无论如何,我的情况的区别不是 JIT/NGEN 而是 Debug/Release。
  • 那么你是问如果配置相同的话,Debug和Release有什么区别?
  • 是的。 PE 中是否有额外的标志(DebuggableAttribute 除外)?最重要的是,动态生成的代码应用了何种级别的优化?
  • 我认为没有?发布版本只是一组预先配置的选项,可让您的应用程序以最佳方式运行。 ASP.NET 应用程序的工作方式不同,因为它们是动态生成的。
【解决方案3】:

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

【讨论】:

  • 我从命令行运行可执行文件进行测量。所以不是这样。
  • 人力资源部。我想这也不会让它比 DEBUG 慢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多