ChatGPT解决这个技术问题 Extra ChatGPT

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

我必须承认,通常我不会在我的程序中的 Debug 和 Release 配置之间进行切换,而且我通常会选择 Debug 配置,即使程序实际部署在客户位置也是如此。

据我所知,如果您不手动更改,这些配置之间的唯一区别是 Debug 定义了 DEBUG 常量,并且 Release 勾选了优化代码

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

这两种配置之间是否存在很大的性能差异。是否有任何特定类型的代码会在这里导致性能上的巨大差异,或者它实际上并不那么重要?是否有任何类型的代码可以在 Debug 配置下正常运行而在 Release 配置下可能会失败,或者您是否可以确定在 Debug 配置下测试并正常工作的代码在 Release 配置下也可以正常工作。


H
Hans Passant

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 = 一个; y = x;变成 y = a;这有助于寄存器分配器做出更好的决定。在 x86 抖动中这是一个大问题,因为它几乎没有可以使用的寄存器。让它选择正确的对性能至关重要。

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

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

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


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

是的,存在许多性能差异,这些差异确实适用于您的代码。 Debug 做的性能优化很少,release 模式做的很多;只有依赖 DEBUG 常量的代码在发布版本中可能会以不同的方式执行。除此之外,您应该看不到任何问题。

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


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

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

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

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


D
Dan Bryant

我的经验是,中型或大型应用程序在发布版本中的响应速度明显更快。试试你的应用程序,看看感觉如何。

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


J
Jason Kresowaty

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


我已经多次发布 Debug 版本,但从未遇到过问题。唯一的区别可能是我们的服务器端应用程序不是一个支持大量用户的网络应用程序。但它是一个处理负载非常高的服务器端应用程序。根据我的经验,Debug 和 Release 之间的区别似乎完全是理论上的。我从未见过我们的任何应用程序有任何实际差异。
P
Pang

我会这样说

很大程度上取决于您的实施。通常,差异不是那么大。我做了很多测量,经常看不出有什么不同。如果您使用非托管代码、大量巨大的数组和类似的东西,性能差异会稍大一些,但不是一个不同的世界(如在 C++ 中)。通常在发布代码中显示更少的错误(更高的容差),因此开关应该可以正常工作。


对于受 IO 限制的代码,发布版本可能不会比调试快。
C
Community

根据我的经验,发布模式带来的最糟糕的事情是晦涩难懂的“发布错误”。由于 IL(中间语言)在 Release 模式下进行了优化,因此存在在 Debug 模式下不会出现的错误的可能性。还有其他关于此问题的 SO 问题:Common reasons for bugs in release version not present in debug mode

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


为了跟进,这里有一篇文章给出了一个发布错误的例子:codeproject.com/KB/trace/ReleaseBug.aspx
如果应用程序使用调试设置进行测试和批准,即使它抑制错误,如果这导致发布构建在部署期间失败,它仍然是一个问题。
D
Darren Rose
    **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.

似乎比在发布模式下有时列表的第一个元素没有正确编号。列表中的一些元素也是重复的。 :)
A
Ashraf AlAssi

我知道我的回答已经很晚了,而且我的回答并不完全符合您的要求,但是,我认为可以使用一些可靠而简单的示例会很好。无论如何,这段代码导致了 Debug 和 Release 之间的巨大差异。代码是在 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;
}

编辑:计算斐波那契数列的性能差异

                       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

测试这样的具体程序是个好主意。您为什么不量化评论中的差异?调试和不调试需要多少秒?
@Thue 实际上,我确实量化了结果,但是我忘了发布它们。
@Thue 我运行了程序并再次进行了测量。我真正不明白的是为什么 C++ x86 (Release) 是最快的?
谢谢你的数字! x86 指针的大小是 x64 指针的一半,因此它们占用的缓存更少,因此缓存中的内容更多。这可能是 x86 更快的原因之一。
@Thue 有趣的原因。调试模式下的 x86 和 x64 怎么样?