“优化编码(Z)” 带来更稳定?还是带来不稳定?- Task.Delay引发的思考
在VS(Microsoft Visual Studio )中进行项目编译的时候,我们可以配置为 Debug 编译,或者配置为Release 编译,当配置为 Release 编译的时候,在生成属性中,有一个可选项为 “优化编码(Z)” ,它是默认开启的。在常规的应用项目中,我们几乎不会太直接地感受到它带来的差异,只知道它可能进行了一些优化,勾选上之后有益无害,很多时候不会太在意,但是我现在遇到一个问题,让我对它的认识,经历了一波三折的变化,像蹦极一样一会儿跳下去一会儿蹦上来。
我有一个硬件控制模块的项目,专门负责与相机的通讯控制,开发阶段是在 Debug 模式下进行调试的,硬件能够稳定地运行,并且没有发现任何的问题,而且在多台电脑上运行也没有遇到任何问题,因此认为这个发板是稳定的。但是后来的某一次发版中 ,使用了 Release 模式编译,心想它应该不会有什么功能差异,至少在我的电脑上完全没有看到任何的差异,但是问题却来了:在客户的电脑上表现出了极其频繁的硬件控制的不稳定情况,频繁出错。
在这个项目中,
功能正常的发布配置为 :.NetFramework 4.6.2+Debug+X64
功能异常的发布配置为 :.NetFramework 4.6.2+Release+AnyCPU
- 尝试 1 :首先我把发布环境调整为了 Release+X64, 但是硬件的命令控制仍然表现不稳定,
- 尝试 2 :然后又完全恢复为了 Debug+X64, 硬件控制果然恢复了稳定, 那么 Debug 和 Release 到底哪里有差异呢 ?
- 尝试 3 :我发现在 Release 编译的时候,勾选了 “优化编码(Z)”,于是我取消了勾选, 禁用代码优化 ,并且 发布配置仍然为 Release+AnyCPU 保持不变 ,结果:硬件的表现是稳定的,正确的 。
因此我得到一个结论 ,就当前的项目而言 ,禁止编译器对代码进行任何的优化,可以保持功能的稳定 ,那么 Release 模式下 编译器到底对代码进行了哪些优化呢 ?
我继续检查硬件控制的核心逻辑,突然发现了一处语法错误:在命令的控制间隔之间有1毫秒的时间间隔 ,但是语法是错误的 :Task.Delay(1), 因为这句代码前面竟然没有加 await, 相当于没有进行任何有效的等待,直接跳过去了,所以现在就变得有点神奇了 :
错误的 Task.Delay(1) 在 Debug 模式下竟然发挥了作用 , 竟然没有出问题 。因此猜测它可能在调试模式下 进行了如下处理:
- 创建Task对象:需要分配内存、初始化定时器
- 调试信息保留:因为每行代码都有断点信息
- 未优化的IL代码:生成完整的中间代码
- 垃圾回收压力:创建的Task对象需要GC处理
这些操作虽然很快,但仍然消耗了 几微秒到几十微秒的时间,虽然没有 await, 但是它所进行的操作所耗费的时长相当于无意中执行了有效的 await;
但是在 Release 编译时,由于启用了代码优化,勾选了 “优化编码(Z)”,编译器可能出于优化的目的 ,完全删除这行代码(死代码消除),结果:循环可能变成近乎 零延迟 的纯 CPU 指令,因此硬件处于被持续叠加命令的状态,完全无喘息地接收硬件指令,导致响应不过来,因此出现了非常大几率的硬件指令失败,从而造成整体几率性不稳定的现象 。
- 尝试 4 :修复了错误的语法之后 ,修改为 await Task.Delay(1), 果然,即使在 Release “优化编码(Z)” 的情况下,软件也恢复了 稳定的状态 , 而且在启用了代码优化的情况下,硬件控制模块整体的性能会更好 。
总结:
本来以为 代码优化之后破坏了测试环境下的稳定 , 最后却发现, “优化编码(Z)” 让我发现了真正的错误所在 , 用 真正的正确 打败 错错为正的假正确 , 来获得 更稳定的硬件表现 和更好的性能,修复后 系统整体表现更流畅,并且任意操作都表现得非常的稳定,达到了更好的状态 。
因此,代码优化不仅能提升性能,还能帮助发现隐藏的逻辑错误。