调试版与发布版性能对比

148
我遇到了以下段落:

在Visual Studio中编译代码时,IDE中的Debug vs. Release设置对性能几乎没有影响...生成的代码几乎相同。 C#编译器实际上并不进行任何优化。 C#编译器只是输出IL代码......在运行时,JITer才执行所有优化。 JITer确实有Debug/Release模式,这对性能有很大影响。但这并不是根据您运行项目的Debug或Release配置,而是取决于调试器是否已连接。

源自这里,播客在这里
有人能将我引导至可以证明这一点的Microsoft文章吗?
通过谷歌搜索“C# debug vs release performance”,大多数结果都表明“Debug会有很大的性能损耗”、“Release是优化的”和“不要在生产环境中部署Debug”。

可能是调试版和发布版构建之间的性能差异的重复问题。 - Hans Passant
1
在Win7-x86上使用.Net4,我编写了一个CPU受限程序,在发布模式下运行几乎比调试模式快2倍,主循环中没有断言等。 - Bengie
1
此外,如果您关心内存使用情况,可能会有很大的差异。我曾经见过这样一个情况:在 Debug 模式下编译的多线程 Windows 服务每个线程使用了 700MB 的内存,而在 Release 构建中每个线程只使用了 50MB 的内存。Debug 构建在典型的使用条件下很快就会耗尽内存。 - o. nate
@Bengie - 你验证过如果在发布版本中附加调试器是否仍然可以使其运行快2倍吗?请注意,上述引用表明JIT优化受到是否附加调试器的影响。 - ToolmakerSteve
11个回答

113

部分正确。在调试模式下,编译器会为所有变量生成调试符号,并按原样编译代码。在发布模式下,会包括一些优化:

  • 未使用的变量根本不会被编译
  • 如果某些循环变量被证明是不变量,则编译器将其排除在循环之外
  • #debug指令下编写的代码不会被包含等等。

其余由JIT完成。

全部优化列表在此处,由Eric Lippert提供。


10
不要忘记 Debug.Asserts!在 DEBUG 构建中,如果它们失败,将会停止线程并弹出消息框。在发布版本中,它们根本就不会被编译。这适用于所有带有 [ConditionalAttribute] 的方法。 - Ivan Zlatanov
13
C#编译器不执行尾递归优化,而是即时编译器进行优化。如果您想要一个准确的列表,了解C#编译器在打开优化开关时做了什么,请参阅http://blogs.msdn.com/ericlippert/archive/2009/06/11/what-does-the-optimize-switch-do.aspx。 - Eric Lippert

69

没有任何文章可以“证明”有关性能问题的任何内容。要证明有关更改性能影响的断言,需要尝试两种方法,并在现实但受控条件下进行测试。

您正在问有关性能的问题,因此显然您关心性能。如果您关注性能,则正确的做法是设置一些性能目标,然后编写自己的测试套件,以跟踪您对这些目标的进展。一旦您拥有了这样的测试套件,就可以轻松使用它来测试“调试构建较慢”等陈述的真实性或虚假性。

而且,您将能够获得有意义的结果。“较慢”是无意义的,因为不清楚它是否比慢一微秒还是慢二十分钟。 “在现实条件下慢10%”更有意义。

把你本来会在网上研究这个问题的时间花在建造一个回答这个问题的设备上。那样你将得到更准确的结果。您在网上阅读的任何内容都只是关于您的程序可能如何行事的“猜测”。根据自己收集的事实推理,而不是根据其他人的猜测来判断程序可能的行为。


3
我认为你可以关注性能,但仍希望使用“调试”功能。例如,如果你大部分的时间都在等待依赖项,我认为以调试模式构建不会有太大的差异,但你将获得更快地修复漏洞和让用户更满意的堆栈跟踪行号信息。关键是要权衡利弊,一般而言,“使用调试会减慢速度,但只有在CPU受限时才会减慢”这样的陈述足以帮助做出决策。 - Josh Mouch

13

我无法对性能进行评论,但建议“不要在生产环境中部署调试代码”仍然适用,因为在大型产品中,调试代码通常会以多种方式执行。首先,您可能已经激活了调试开关,其次可能会有额外的冗余安全检查和调试输出,这些都不属于生产代码。


我同意你在那个问题上的看法,但这并没有回答主要问题。 - sagie
5
@sagie:是的,我知道,但我认为这个观点仍然值得提出。 - Konrad Rudolph

6

来自msdn社区

这个问题并没有很好的文档,以下是我知道的情况。编译器会发出一个System.Diagnostics.DebuggableAttribute实例。在调试版本中,IsJitOptimizerEnabled属性为True,在发布版本中则为False。您可以使用ildasm.exe在程序集清单中看到此属性。

JIT编译器使用此属性禁用会使调试困难的优化,例如移动代码的循环不变式提升。在某些情况下,这可能会对性能产生重大影响,但通常不会。

将断点映射到执行地址是调试器的工作。它使用.pdb文件和JIT编译器生成的信息来提供IL指令到代码地址的映射。如果您要编写自己的调试器,则需要使用ICorDebugCode::GetILToNativeMapping()。

基本上,调试部署会更慢,因为JIT编译器的优化被禁用了。


4
您所阅读的内容是非常正确的。由于JIT优化,发布通常更加精简,不包括调试代码(#IF DEBUG或[Conditional("DEBUG")]),最小化调试符号加载,并且往往不被认为是较小的程序集,这将减少加载时间。在VS中运行代码时,由于加载了更广泛的PDB和符号,性能差异更加明显,但如果您独立运行它,则性能差异可能不太明显。某些代码将比其他代码更好地进行优化,并且它使用与其他语言相同的优化启发式方法。
Scott在内联方法优化方面有一个很好的解释,请单击此处
请参见本文,其中对ASP.NET环境下的调试和发布设置的区别进行了简要说明。

3
msdn 网站中...

发布 vs. 调试 配置

当您仍在开发项目时,通常会使用调试配置构建应用程序,因为此配置使您能够查看变量的值并通过调试器控制执行。您还可以创建和测试发布配置的构建以确保您没有引入任何仅在一种构建类型上表现的错误。在.NET Framework编程中,这样的错误非常罕见,但它们可能会发生。

当您准备将应用程序分发给最终用户时,请创建一个发布版本,它将比对应的调试配置更小,通常性能也更好。您可以在“项目设计器”的“生成”面板或“生成”工具栏中设置构建配置。有关更多信息,请参阅“构建配置”。


3

需要注意的一点是,关于性能和调试器是否附加,有些事情会让我们感到惊讶。

我们有一段代码,包含许多紧密循环,似乎在调试时要花费很长时间,但单独运行时却运行得很好。换句话说,没有客户或客户端遇到问题,但在调试时似乎运行得很慢。

罪魁祸首是一个Debug.WriteLine在其中一个紧密循环中,它输出了数千条日志消息,留下了一段时间以前的调试会话。看来当调试器附加并监听这样的输出时,涉及到的开销会减慢程序的速度。对于这个特定的代码,它在自己的运行时是0.2-0.3秒,而在调试器附加时则为30+秒。

简单的解决方案是,只需删除不再需要的调试消息即可。


3
我最近遇到了性能问题。产品完整列表需要太长时间,大约80秒。我调优了数据库,改进了查询,但没有任何区别。我决定创建一个测试项目,结果发现相同的过程只需4秒钟就可以执行。然后我意识到主项目处于调试模式,而测试项目处于发布模式。我将主项目切换到发布模式,产品完整列表仅需4秒钟即可显示所有结果。
总结:调试模式比运行模式慢得多,因为它保留调试信息。您应该始终在发布模式下部署。如果包含.PDB文件,则仍然可以获得调试信息。这样,您就可以记录带有行号的错误,例如。

你说的“run mode”是指“发布模式”吗? - Ron Klein
是的,没错。发布版本没有所有的调试开销。 - Francisco Goldenstein
我给你点赞,因为你是唯一一个提供了一些真实比较的答案。 - S Nash

1
在很大程度上,这取决于您的应用程序是否受计算限制,而且有时很难判断,就像Lasse的例子一样。如果我对它正在做的事情有丝毫疑问,我会暂停几次并检查堆栈。如果有一些额外的东西正在进行,而我实际上并不需要,那么这会立即发现。

1

调试模式和发布模式有差异。有一个工具Fuzzlyn:它是一个利用Roslyn生成随机C#程序的模糊测试器。它在.NET Core上运行这些程序,并确保它们在调试和发布模式下编译时给出相同的结果。

使用这个工具,发现并报告了很多错误。


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