在C#中优化代码是否存在风险?

在VS2010 Pro的构建设置面板中,有一个标签为“优化代码”的CheckBox ……当然,我想检查一下……但是要非常谨慎,我问我的兄弟,并且他说它未经检查以进行调试,并且在C ++中它可能会做一些会破坏或破坏代码的事情……但他不了解C#。

所以我的问题是,我可以在此框中查看我的发布版本而不必担心它会破坏我的代码吗? 第二,如果它可以破坏代码,何时以及为什么? 链接到解释欢迎。

我正在谈论的CheckBox。

您通常会在发布版本中使用此选项。 这样做是安全和主流的。 没有理由害怕在启用优化的情况下发布代码。 启用优化可能会干扰调试,这是禁用调试版本的一个很好的理由。

优化不应该真正破坏您的代码。 Eric Lippert 在这里有一篇文章解释了当你打开那面旗子时会发生什么。 性能增益因应用程序而异,因此您需要使用项目对其进行测试,以确定是否存在任何明显的差异(在性能方面)。

在发布模式下运行时可能会发生一些错误,否则会发生错误。 想到臭名昭着的“非挥发性旗帜”:

flag = false; Thread t = new Thread( o => { while(!flag) { // do stuff } }); t.Start(); // main thread does some work flag = true; t.Join(); // will never return in release mode if flag is not volatile 

这是因为编译器优化,因为标志变量被线程t的核心缓存,因此它无法看到标志的更新值。

优化应该引入错误吗? 没有。

可以优化引入错误吗? 也许,毕竟没有什么是完美的。

操作人员是否可以发现代码中始终存在的错误,但在关闭时会隐藏错误? 绝对的,发生了很多。

重要的是要意识到这是一个变化。 就像你测试你是否做了很多改变一样,你应该测试你何时关闭它们。 如果最终版本打开它们,那么最终测试也必须打开它们。

在C#中,优化应该永远不会破坏您的代码。

相反,在启用优化的情况下,编译器在C#和CIL之间进行转换时会生成更紧凑的CIL。

我观察(并且坦率地说它很有趣!)来自.NET <2.0(1.0和1.1)的C#编译器产生了良好的CIL而没有优化,因为后来的C#编译器(2.0及更高版本)产生了WITH优化。

示例明智我有一段代码来自我的硕士论文的一些模拟部分。 在打开优化标志的情况下,代码并没有真正破坏程序,但是路径查找器只执行一次运行和循环。 (递归代码将自身陷入路径查找器的循环中,它始终在关闭优化标志的情况下突破)。

所以是的,优化标志可能使软件的行为不同。

.net编译器优化可能会导致错误。 今天发生在我身上。 我花了几个小时来钉它。 代码是:

 for (int i = 0; i < list.Count-1; i++) { list[i+1].DoSomeThing(); //some code if (someCondition) { list.insert(i+1, new Item()); i++; } } 

在某些时候, list[i+1]被列为list[i] ,就像两者都指向同一个项目一样。 这个bug真是太奇怪了。 代码在调试模式和发布模式下运行良好,但是当我将它运行到侧面视觉工作室时,例如。 从.exe文件中,代码崩溃了。 只关闭编译器优化修复它。

在我的情况下,当我打开优化标志时,它将无法完成所有操作,因此最终结果中缺少测量点,因此我只是关闭优化标志以修复错误:

 using System.Threading.Tasks; Parallel.Invoke( async () => await ProcessPartialArrayOperationAssets(operationAssets, 0, operationAssets.Count / 2, operations, inspection1), async () => await ProcessPartialArrayOperationAssets(operationAssets, operationAssets.Count / 2, operationAssets.Count, operations, inspection1) ); private async Task ProcessPartialArrayInspectionOperations(IList operations, int begin, int end, Inspection inspection, InspectionAsset inspectionAsset) { await Task.Run(() => { // create one new operation measuring point for each measuring point in the operation's equipment int itemCounter = begin + 1; for (int i = begin; i < end; i++) { lock (_thisLock) { InspectionOperation operation = operations[i]; int itemNumber = 1; // get the asset InspectionAsset operationAsset = operation.OperationAsset; if (operationAsset != null) { // get the measuring points string ABAPTrue = Abap.ABAP_TRUE; lock (_thisLock) { IList measuringPoints = DbContext.MeasuringPoints.Where(x => x.AssetID == operationAsset.AssetID && x.InactiveFlag != ABAPTrue) .ToList(); if (measuringPoints != null) { //Debug.WriteLine("measuringPoints.Count = " + measuringPoints.Count); // create the operation measuring points foreach (MeasuringPoint measuringPoint in measuringPoints) { OperationMeasuringPoint operationMeasuringPoint = new OperationMeasuringPoint { InspectionID = inspection.InspectionID, OperationNumber = operation.OperationNumber, SubActivity = "", RoutingNo = "", ItemNumber = itemNumber.ToString("D4"), // eg "0001", "0002" and so on ItemCounter = itemCounter.ToString("D8"), // eg "00000001", "00000002" and so on MeasuringPointID = measuringPoint.MeasuringPointID, MeasuringPointDescription = measuringPoint.Description, Equipment = inspectionAsset.AssetID, Category = "P" }; DbContext.Entry(operationMeasuringPoint).State = EntityState.Added; itemNumber++; itemCounter++; } } } } } } }); } 

因此我也用这个替换了Parallel.Invoke调用。 仅供参考,使用.NET Framework 4.7发生此问题。

 await ProcessPartialArrayOperationAssets(operationAssets, 0, operationAssets.Count, operations, inspection1); 

更新:

好的,我发现如果从方法签名中删除async Task ,我可以重新启用优化标志并使用Parallel.Invoke

  private void ProcessPartialArrayInspectionOperations(IList operations, int begin, int end, Inspection inspection, InspectionAsset inspectionAsset) { // create one new operation measuring point for each measuring point in the operation's equipment int itemCounter = begin + 1; for (int i = begin; i < end; i++) { InspectionOperation operation = operations[i]; int itemNumber = 1; // get the asset InspectionAsset operationAsset = operation.OperationAsset; if (operationAsset != null) { // get the measuring points string ABAPTrue = Abap.ABAP_TRUE; lock (_thisLock) { IList measuringPoints = DbContext.MeasuringPoints.Where(x => x.AssetID == operationAsset.AssetID && x.InactiveFlag != ABAPTrue) .ToList(); if (measuringPoints != null) { //Debug.WriteLine("measuringPoints.Count = " + measuringPoints.Count); // create the operation measuring points foreach (MeasuringPoint measuringPoint in measuringPoints) { OperationMeasuringPoint operationMeasuringPoint = new OperationMeasuringPoint { InspectionID = inspection.InspectionID, OperationNumber = operation.OperationNumber, SubActivity = "", RoutingNo = "", ItemNumber = itemNumber.ToString("D4"), // eg "0001", "0002" and so on ItemCounter = itemCounter.ToString("D8"), // eg "00000001", "00000002" and so on MeasuringPointID = measuringPoint.MeasuringPointID, MeasuringPointDescription = measuringPoint.Description, Equipment = inspectionAsset.AssetID, Category = "P" }; DbContext.Entry(operationMeasuringPoint).State = EntityState.Added; itemNumber++; itemCounter++; } } } } } } Parallel.Invoke( () => ProcessPartialArrayInspectionOperations(operations, 0, operations.Count / 2, inspection1, inspectionAsset), () => ProcessPartialArrayInspectionOperations(operations, operations.Count / 2, operations.Count, inspection1, inspectionAsset) ); 

或者,我想我可以为每个使用Task.Run ,然后等待Task.WhenAll(t1, t2, t3); 正如这里所解释的那样,但是在这种情况下,我没有进行显式的数据库调用,所以我不认为它适用于使用Task.Run而不是Parallel.Invoke虽然这个页面确实解释了为什么我的Parallel.Invoke没有完成: Parallel.Invoke不等待异步方法完成

有关详细信息,请参阅“C#中的并发” https://stephencleary.com/book/