调试和发行版之间的性能差异


280

我必须承认,通常我不会在程序中的DebugRelease配置之间进行切换,并且即使在程序实际部署在客户位置的情况下,我通常也选择使用Debug配置。

据我所知,如果不手动更改,则这些配置之间的唯一区别是Debug具有DEBUG定义的常量,而Release具有已检查的Optimize代码

所以我的问题实际上是双重的:

  1. 这两种配置之间在性能上有很多区别吗?是否有任何特定类型的代码会在这里造成性能上的巨大差异,或者实际上不是那么重要吗?

  2. 是否有任何类型的代码都可以在Debug配置下正常运行,而在Release配置下可能会失败,或者您可以确定经过测试并且可以在Debug配置下正常运行的代码也可以在Release配置下正常运行。


Answers:


511

C#编译器本身不会在Release版本中对发出的IL进行很大的更改。值得注意的是,它不再发出允许您在花括号上设置断点的NOP操作码。最大的一个是内置在JIT编译器中的优化器。我知道它可以进行以下优化:

  • 方法内联。方法调用由注入方法的代码代替。这是一个很大的问题,它使属性访问器基本上免费。

  • CPU寄存器分配。局部变量和方法参数可以保持存储在CPU寄存器中,而无需(或不经常)存储回堆栈帧。这是一个很大的问题,值得注意的是使调试优化的代码变得如此困难。并为volatile关键字赋予含义。

  • 消除数组索引检查。使用数组时的一项重要优化(所有.NET集合类在内部使用数组)。当JIT编译器可以验证循环永远不会索引数组超出范围时,它将消除索引检查。大的一个。

  • 循环展开。通过在主体中重复执行多达4次且减少循环的代码,可以改善具有小主体的循环。降低分支成本并改善处理器的超标量执行选项。

  • 消除无效代码。像if(false){/ ... /} 这样的语句被完全消除。这可能是由于不断折叠和内嵌而发生的。在其他情况下,JIT编译器可以确定代码没有可能的副作用。这种优化使分析代码如此棘手。

  • 代码提升。不受循环影响的循环内的代码可以移出循环。C编译器的优化器将花费更多的时间来寻找提升的机会。但是,由于需要进行数据流分析,因此这是一项昂贵的优化,抖动无法承受时间,因此只能举起明显的情况。迫使.NET程序员编写更好的源代码并自行提升。

  • 常见子表达式消除。x = y + 4; z = y + 4; 变成z = x; 在dest [ix + 1] = src [ix + 1]等语句中很常见;为提高可读性而编写,而没有引入辅助变量。无需牺牲可读性。

  • 不断折叠。x = 1 + 2; 变成x = 3; 这个简单的示例早已被编译器捕获,但发生在JIT时,其他优化使之成为可能。

  • 复制传播。x = a; y = x; 变成y = a; 这有助于寄存器分配器做出更好的决策。x86抖动非常重要,因为它很少有寄存器可以使用。选择正确的选择对性能至关重要。

这些非常重要的优化可以带来很大的不同,例如,当您分析应用程序的Debug版本并将其与Release版本进行比较时。这只是真正重要,尽管当代码位于关键路径上时,所编写的代码的5%至10%实际上会影响程序的性能。JIT优化器不够聪明,无法预先知道什么是关键代码,它只能对所有代码应用“将其转到11”拨盘。

这些优化对程序执行时间的有效结果通常受在其他位置运行的代码的影响。读取文件,执行dbase查询等。使工作变得完全不可见。它不在乎:)

JIT优化器是非常可靠的代码,主要是因为它已经经受了数百万次测试。在程序的发布版本中出现问题的情况很少见。但是确实发生了。x64和x86抖动都存在结构问题。x86抖动在浮点一致性方面存在问题,当浮点计算的中间值以80位精度保存在FPU寄存器中,而不是在刷新到内存时会被截断时,会产生完全不同的结果。


23
我不认为所有集合都不会使用array(s):LinkedList<T>不会,即使它不是很经常使用。
svick 2011年

我认为CLR将FPU配置为53位精度(匹配64位宽的双精度),因此对于Float64值,不应有80位扩展的双精度计算。但是,Float32计算可能会以这种53位精度进行计算,并且只有在存储到内存时才会被截断。
Govert,2012年

2
volatile关键字不适用于存储在堆栈帧的局部变量。从msdn.microsoft.com/zh-cn/library/x13ttww7.aspx上的文档中:“ volatile关键字只能应用于类或结构的字段。不能将局部变量声明为volatile。”
克里斯·范德莫顿

8
作为一个不起眼的修正,我想真正使之间的差异DebugRelease建立在这方面是“优化代码”复选框,这通常是上Release不过关的Debug。只是要确保读者不要开始认为这两个构建配置之间存在着“不可思议的”,看不见的差异,这些差异超出了Visual Studio中项目属性页的范围。
chiccodoro 2014年

3
也许值得一提的是,System.Diagnostics.Debug上的所有方法实际上都没有在调试版本中执行任何操作。另外,变量没有那么快就可以最终确定,请参阅(stackoverflow.com/a/7165380/20553)。
马丁·布朗

23
  1. 是的,存在许多性能差异,这些差异确实适用于您的所有代码。Debug很少进行性能优化,而释放模式非常多;

  2. 只有依赖DEBUG常量的代码在发布版本中可能会有所不同。除此之外,您应该不会看到任何问题。

依赖于DEBUG常量的框架代码的一个示例是Debug.Assert()方法,该方法具有已[Conditional("DEBUG)"]定义的属性。这意味着它还取决于DEBUG常数,并且它不包含在发行版本中。


2
都是如此,但是您能衡量出差异吗?还是在使用程序时注意到差异?当然,我不想鼓励任何人在调试模式下发布他们的软件,但是问题是,是否存在巨大的性能差异,我看不到这一点。
testalino

2
还值得注意的是,调试版本与原始源代码的关联程度远高于发行版。如果您认为(但是不太可能)有人尝试对您的可执行文件进行反向工程,则您不希望通过部署调试版本来简化它们。
jwheron

2
@testalino-好吧,这些天很难。处理器已经变得如此之快,以至于由于用户的行为,用户几乎不等待进程实际执行代码,因此这是相对的。但是,如果您实际上在做一些漫长的过程,是的,您会注意到。下面的代码例如运行40%慢下DEBUGAppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length))
Pieter van Ginkel 2010年

2
另外,如果您asp.net使用调试而不是发行版,则可能会在页面上添加一些脚本,例如:MicrosoftAjax.debug.js大约7k行。
BrunoLM

13

这在很大程度上取决于应用程序的性质。如果您的应用程序是大量使用UI的,那么您可能不会注意到任何区别,因为连接到现代计算机的最慢的组件是用户。如果使用某些UI动画,则可能需要测试在DEBUG构建中运行时是否可以感知到任何明显的延迟。

但是,如果您有许多计算量大的计算,则您会注意到差异(可能高达@Pieter提到的40%,尽管这取决于计算的性质)。

基本上,这是设计上的折衷。如果要在DEBUG版本下发布,那么如果用户遇到问题,则可以获得更有意义的回溯,并且可以进行更灵活的诊断。通过发布DEBUG版本,您还可以避免优化器产生模糊的Heisenbugs


11
  • 我的经验是,中型或大型应用程序在Release版本中的响应速度明显更快。试试看您的应用程序,看看它的感觉。

  • 可能会对Release版本产生影响的一件事是,Debug版本代码有时可以抑制竞争条件和其他与线程相关的错误。优化的代码可能会导致指令重新排序,更快的执行可能会加剧某些竞争条件。


9

您永远不应将.NET Debug版本发布到生产环境中。它可能包含难看的代码以支持“编辑并继续”功能,或者谁知道其他什么。据我所知,这仅发生在VB中,而不是C#中(请注意:原始文章被标记为C#),但它仍应为暂停Microsoft认为允许他们使用Debug版本所做的事情提供理由。实际上,在.NET 4.0之前,VB代码泄漏的内存与具有为支持“编辑并继续”而构造的事件的对象实例数量成比例。(尽管据报告,此问题已根据生成的代码https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging进行了修复看起来很讨厌,创建WeakReference对象并将它们添加到静态列表中,同时持有锁)我当然不希望在生产环境中提供任何这种调试支持!


我已经多次发布了Debug版本,但从未遇到问题。也许唯一的区别是,我们的服务器端应用程序不是支持大量用户的Web应用程序。但这是具有很高处理负载的服务器端应用程序。根据我的经验,Debug和Release之间的区别似乎完全是理论上的。我从未见过与我们的任何应用程序有任何实际差异。
山姆·戈德堡

5

以我的经验,发布模式中最糟糕的事情是晦涩的“发布错误”。由于IL(中间语言)在Release模式下进行了优化,因此存在可能在Debug模式下未发现的错误。还有其他SO问题解决此问题: 调试模式中不存在发行版中的错误的常见原因

这发生在我身上一两次,在一个简单的控制台应用程序在“调试”模式下可以完美运行,但是如果输入完全相同,则在“发布”模式下会出错。这些错误极其难以调试(具有讽刺意味的是,根据发布模式的定义)。


为了跟进,下面的文章提供了一个发行错误的示例:codeproject.com/KB/trace/ReleaseBug.aspx
Roly

如果使用Debug设置测试并批准了该应用程序,即使该应用程序抑制了错误,仍然会导致问题,即使这会导致发行版本在部署过程中失败。
岛之风Bråthen如此阐述

4

我会说1)在很大程度上取决于您的实现。通常,差异并不大。我做了很多测量,但常常看不到差异。如果使用非托管代码,大量数组和类似的东西,则性能差异会稍大一些,但世界不会有所不同(例如C ++)。2)通常,在发布代码中,显示的错误更少(较高的容差),因此,开关应该可以正常工作。


1
对于受IO约束的代码,发布版本可能很容易调试得很快。
理查德

0
    **Debug Mode:**
    Developer use debug mode for debugging the web application on live/local server. Debug mode allow developers to break the execution of program using interrupt 3 and step through the code. Debug mode has below features:
   1) Less optimized code
   2) Some additional instructions are added to enable the developer to set a breakpoint on every source code line.
   3) More memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are not cached.
   5) It has big size, and runs slower.

    **Release Mode:**
    Developer use release mode for final deployment of source code on live server. Release mode dlls contain optimized code and it is for customers. Release mode has below features:
   1) More optimized code
   2) Some additional instructions are removed and developer cant set a breakpoint on every source code line.
   3) Less memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are cached.
   5) It has small size, and runs fast.

2
似乎在发布模式下,有时列表的前几个元素编号不正确。列表中的某些元素也是重复的。:)
吉安·保罗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.