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

我必须承认,通常我不会在程序中的调试释放配置之间切换,而且我通常选择调试配置,即使程序实际部署在客户的位置。

据我所知,如果不手动更改,这些配置之间的唯一区别是调试定义了DEBUG常量,而释放检查了优化代码

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

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

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

74577 次浏览
  1. 是的,存在许多性能差异,这些差异确实适用于您的所有代码。调试很少做性能优化,而发布模式做得很多;

  2. 只有依赖于DEBUG常量的代码才能在发行版中表现不同。除此之外,你应该看不到任何问题。

依赖于DEBUG常量的框架代码的一个例子是Debug.Assert()方法,它定义了属性[Conditional("DEBUG)"]。这意味着它也依赖于DEBUG常量,而这并不包含在发布版本中。

我会说

  1. 很大程度上取决于你的实现。通常情况下,差别并没有那么大。我做了很多测量,但经常看不到区别。如果你使用非托管代码,大量的巨大数组和类似的东西,性能的差异会稍微大一些,但不是一个不同的世界(像在c++中)。

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

根据我的经验,在发布模式中出现的最糟糕的事情就是晦涩的“发布漏洞”。由于IL(中间语言)是在发布模式下优化的,因此存在在调试模式下不会出现的错误的可能性。还有其他关于这个问题的SO问题: 在调试模式中不存在发布版本中的错误的常见原因 < / p >

这种情况在我身上发生过一两次,一个简单的控制台应用程序在调试模式下运行得很好,但给定完全相同的输入,在发布模式下就会出错。这些bug极其难以调试(讽刺的是,根据发布模式的定义)。

  • 我的经验是,中等大小或较大的应用程序在发布版构建中反应明显更好。在您的应用程序中尝试一下,看看效果如何。

  • 发布版本可能会让您感到困扰的一件事是,调试版本代码有时会抑制竞态条件和其他与线程相关的错误。优化的代码可能导致指令重新排序,更快的执行可能加剧某些竞争条件。

这在很大程度上取决于应用程序的性质。如果您的应用程序是ui密集型的,您可能不会注意到任何不同,因为连接到现代计算机的最慢的组件是用户。如果您使用一些UI动画,您可能想要测试在DEBUG版本中运行时是否能察觉到任何明显的延迟。

然而,如果您有很多计算量大的计算,那么您就会注意到差异(可能高达40%,正如@Pieter所提到的,尽管这取决于计算的性质)。

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

在发布版本中,c#编译器本身并没有对发出的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抖动中是一个大问题,因为它只有很少的寄存器可以使用。让它选择正确的选项对性能至关重要。

这些都是非常重要的优化,可以使伟大的交易的差异,例如,当你剖析你的应用程序的调试版本,并将其与发布版本进行比较。只有当代码在你的关键路径上时,这才真正重要,你写的5%到10%的实际上代码会影响你程序的性能。JIT优化器不够聪明,不能预先知道什么是关键的,它只能对所有代码应用“将它转到11”的拨号盘。

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

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

永远不要将. net Debug版本发布到生产环境中。它可能包含丑陋的代码来支持编辑-继续或谁知道其他什么。据我所知,这种情况只发生在VB中,而不是c# (注:原文标签为c#)中,但它仍然应该有理由暂停微软认为他们可以对调试构建做什么。事实上,在。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对象,并将它们添加到静态列表,而握着一把锁)我当然不想在生产环境中任何这种调试支持!

    **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 can’t 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.

我知道我的答案很晚,我的答案并不完全是你想要的,但是,我认为一些坚实而简单的例子会很好。无论如何,这段代码导致调试释放之间的巨大的差异。代码是在Visual Studio 2019上用c++编写的。代码是这样的:

#include <iostream>


using namespace std;


unsigned long long fibonacci(int n)
{
return n < 2 ? n : (fibonacci(n - 1) + fibonacci(n - 2));
}


int main()
{
int x = 47;


cout << "Calculating..." << endl;
cout << "fib(" << x << ") = " << fibonacci(x) << endl;
}
< p >编辑: 计算斐波那契数列的性能差异

                       Debug        Release
C++ x86 C++ x64 C++ x86 C++ x64 C# Debug    C# Release
Time (mSeconds) 99384.9 27799.1 11066.0 11321.5 95233.7 24566.0
Time (Seconds)  99.4    27.8    11.1    11.3    95.2    24.6