一尘不染

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

c#

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

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

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

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

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


阅读 361

收藏
2020-05-19

共1个答案

一尘不染

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寄存器中,而不是在刷新到内存时会被截断时,会产生完全不同的结果。

2020-05-19