我必须承认,通常我没有在程序中调试Debug和Release配置之间进行切换,我通常选择调试配置,即使程序实际部署在客户端也是如此 .
据我所知,如果不手动更改这些配置,这些配置之间的唯一区别是Debug已定义 DEBUG 常量,并且Release已检查Optimize代码 .
所以我的问题实际上是双重的:
-
这两种配置之间是否有很多性能差异 . 是否有任何特定类型的代码会在这里引起性能上的巨大差异,或者它实际上并不那么重要?
-
是否有任何类型的代码在Debug配置下运行正常,可能在Release配置下失败,或者您可以确定在Debug配置下经过测试和正常工作的代码在Release配置下也能正常工作 .
8 回答
我的经验是,在发布版本中,中型或大型应用程序的响应速度明显提高 . 尝试使用您的应用程序,看看它的感觉 .
使用Release版本可以咬你的一件事是,Debug构建代码有时可以抑制竞争条件和其他与线程相关的错误 . 优化的代码可能导致指令重新排序,更快的执行会加剧某些竞争条件 .
是的,存在许多性能差异,这些差异确实适用于您的所有代码 . Debug几乎没有性能优化和释放模式;
只有依赖于
DEBUG
常量的代码才能对发布版本执行不同的操作 . 除此之外,你不应该看到任何问题 .依赖于
DEBUG
常量的框架代码示例是Debug.Assert()
方法,其定义了属性[Conditional("DEBUG)"]
. 这意味着它还取决于DEBUG
常量,并且这不包含在发布版本中 .根据我的经验,发布模式中最糟糕的事情是模糊的"release bugs" . 由于IL(中间语言)在发布模式下进行了优化,因此存在错误的可能性,这些错误在调试模式下不会出现 . 还有其他SO问题可以解决这个问题:Common reasons for bugs in release version not present in debug mode
这种情况发生在我身上一两次,一个简单的控制台应用程序在调试模式下运行完全正常,但是如果输入完全相同,则会在发布模式下出错 . 这些错误非常难以调试(根据发布模式的定义,具有讽刺意味) .
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优化器不够智能,无法预先知道什么是关键,它只能为所有代码应用"turn it to eleven"拨号 .
这些优化对程序执行时间的有效结果通常会受到在其他地方运行的代码的影响 . 读取文件,执行dbase查询等 . 使JIT优化器的工作完全不可见 . 它不介意:)
JIT优化器是非常可靠的代码,主要是因为它已经被测试了数百万次 . 在程序的Release版本中出现问题极为罕见 . 然而它确实发生了 . x64和x86抖动都遇到了结构问题 . x86抖动在浮点一致性方面存在问题,当浮点计算的中间体以80位精度保存在FPU寄存器中而不是在刷新到内存时被截断时,会产生微妙的不同结果 .
这在很大程度上取决于您的应用程序的性质 . 如果您的应用程序是UI重的,您可能不会注意到任何差异,因为连接到现代计算机的最慢组件是用户 . 如果您使用某些UI动画,则可能需要测试在DEBUG构建中运行时是否可以感知到任何明显的延迟 .
但是,如果你有许多计算量很大的计算,那么你会发现差异(可能高达40%,正如@Pieter所提到的,尽管它取决于计算的性质) .
它在DEBUG构建下重新发布,然后如果用户遇到问题,您可以获得更有意义的回溯,并且您可以进行更灵活的诊断 . 通过在DEBUG构建中释放,您还可以避免优化器产生模糊的Heisenbugs .
You should never release a .NET Debug build into production. 它可能包含丑陋的代码以支持编辑并继续或谁知道还有什么 . 据我所知,这只发生在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
对象并在保持锁定的同时将它们添加到静态列表中)我当然不希望在 生产环境 环境中有任何这种调试支持!我会说1)在很大程度上取决于你的实施 . 通常情况下,差异并不大 . 我做了很多测量,经常看不出差异 . 如果你使用非托管代码,许多大型数组和类似的东西,性能差异稍大,但不是一个不同的世界(如C) . 2)通常在发布代码中显示更少的错误(更高的容差),因此开关应该正常工作 .