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

Øyv*_*hen 273 .net c# debugging performance configuration

我必须承认,通常我没有在程序中调试DebugRelease配置之间进行切换,我通常选择调试配置,即使程序实际部署在客户端也是如此.

据我所知,如果不手动更改这些配置,这些配置之间的唯一区别是DebugDEBUG定义常量,并且Release已检查Optimize代码.

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

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

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

Han*_*ant 505

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抖动中是一个大问题,因为它几乎没有寄存器可供使用.选择合适的选项对于perf来说至关重要.

这些是非常重要的优化,例如,当您分析应用程序的Debug构建并将其与Release构建进行比较时,这些优化会产生很大的差异.只有当代码在你的关键路径上时,这才真正重要,你编写的5到10%的代码实际上会影响程序的性能.JIT优化器不够智能,无法预先知道什么是关键,它只能为所有代码应用"转到11"拨号.

这些优化对程序执行时间的有效结果通常受到在其他地方运行的代码的影响.读取文件,执行dbase查询等.使JIT优化器的工作完全不可见.它不介意:)

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

  • 我不认为*all*集合使用数组:`LinkedList <T>`没有,即使它不经常使用. (23认同)
  • 作为一个不起眼的修正案,我想在这方面真正使"调试"和"释放"构建之间的区别在于"优化代码"复选框,它通常用于"发布"而是用于"调试".这只是为了确保读者不要开始认为两个构建配置之间存在"神奇",无形的差异,这些差异超出了Visual Studio中项目属性页面上的内容. (8认同)
  • 或许值得一提的是,System.Diagnostics.Debug上几乎没有任何方法可以在调试版本中执行任何操作.此外,变量也不能很快完成(http://stackoverflow.com/a/7165380/20553). (3认同)
  • `volatile`关键字不适用于存储在堆栈帧中的局部变量.来自http://msdn.microsoft.com/en-us/library/x13ttww7.aspx上的文档:"volatile关键字只能应用于类或结构的字段.局部变量不能声明为volatile." (2认同)
  • @chiccodoro - 实际上,根据 Hans 的说法以及其他地方的引用,C# 的最大区别不是“优化代码”复选框,而是 JIT 是否在调试模式或发布模式下运行。这取决于是否附加了调试器,而不是由该复选框或 C# 编译器完成的任何操作决定,甚至也取决于您是否在“调试”或“发布”中构建。如果我理解正确的话,如果你将调试器附加到发布进程,你就会失去汉斯上面提到的所有优化。 (2认同)

Pie*_*kel 23

  1. 是的,有许多性能差异,这些确实适用于您的所有代码.Debug几乎没有性能优化和释放模式;

  2. 只有依赖于DEBUG常量的代码才能对发布版本执行不同的操作.除此之外,你不应该看到任何问题.

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

  • 这一切都是真的,但你能衡量一下差异吗?或者在使用程序时注意到差异?当然我不想鼓励任何人在调试模式下发布他们的软件,但问题是如果存在巨大的性能差异而且我看不到. (2认同)
  • 另外值得注意的是,调试版本与原始源代码的关联程度远高于发布版本.如果您认为(但不太可能)有人可能尝试对您的可执行文件进行逆向工程,您不希望通过部署调试版本来使它们更容易. (2认同)
  • @testalino - 好吧,这几天很难.由于用户操作,处理器的速度很快,用户很难等待进程实际执行代码,所以这一切都是相对的.但是,如果你真的做了一些漫长的过程,是的,你会注意到.下面的代码例如在`DEBUG`下运行速度慢40%:`AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length))`. (2认同)
  • 此外,如果你在`asp.net`并使用debug而不是release,你的页面上可能会添加一些脚本,例如:`MicrosoftAjax.debug.js`,它有大约7k行. (2认同)

Lie*_*yan 12

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

但是,如果你有许多计算量很大的计算,那么你会注意到差异(可能高达@Pieter提到的40%,尽管它取决于计算的性质).

这基本上是一种设计权衡.如果您在DEBUG构建下发布,那么如果用户遇到问题,您可以获得更有意义的回溯,并且您可以进行更灵活的诊断.通过在DEBUG构建中发布,您还可以避免优化器生成模糊的Heisenbugs.


Dan*_*ant 11

  • 我的经验是,在发布版本中,中型或大型应用程序的响应速度明显提高.尝试使用您的应用程序,看看它的感觉.

  • 使用Release版本可能会让您感到困惑的一点是,Debug构建代码有时可以抑制竞争条件和其他与线程相关的错误.优化的代码可能导致指令重新排序,更快的执行会加剧某些竞争条件.


Jas*_*aty 9

您永远不应该将.NET Debug构建版本发布到生产中.它可能包含丑陋的代码以支持编辑和继续或谁知道还有什么.据我所知,这只发生在VB而不是C#中(注意:原始帖子被标记为C#),但是它应该仍然有理由暂停微软认为允许使用Debug构建的内容.事实上,在.NET 4.0之前,VB代码会泄漏内存,这些内存与您构建的支持Edit-and-Continue的事件的对象实例数成正比.(虽然据报道这是根据https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging修复的,但生成的代码看起来很讨厌,创建WeakReference对象并将它们添加到静态列表中持有锁)我当然不希望在生产环境中有任何这种调试支持!


tes*_*ino 5

我会这样说

  1. 很大程度上取决于您的实施。通常,差异并不大。我做了很多测量,但经常看不出有什么不同。如果您使用非托管代码、大量大型数组之类的东西,性能差异会稍大一些,但不是一个不同的世界(如在 C++ 中)。

  2. 通常在发布代码中显示的错误较少(更高的容忍度),因此开关应该可以正常工作。


Rol*_*oly 5

根据我的经验,发布模式中最糟糕的事情是模糊的"发布错误".由于IL(中间语言)在发布模式下进行了优化,因此存在在调试模式下不会出现错误的可能性.还有其他SO问题涉及此问题: 调试模式中不存在发布版本中的错误的常见原因

这种情况发生在我身上一两次,一个简单的控制台应用程序在调试模式下运行完全正常,但是在给定完全相同的输入的情况下,在发布模式下会出错.这些错误非常难以调试(根据发布模式的定义,具有讽刺意味).