简体   繁体   中英

Are there risks to optimizing code in C#?

In the build settings panel of VS2010 Pro, there is a CheckBox with the label "optimize code"... of course, I want to check it... but being unusually cautious, I asked my brother about it and he said that it is unchecked for debugging and that in C++ it can potentially do things that would break or bug the code... but he doesn't know about C#.

So my question is, can I check this box for my release build without worrying about it breaking my code? Second, if it can break code, when and why? Links to explanations welcome.

我正在谈论的复选框。

You would normally use this option in a release build. It's safe and mainstream to do so. There's no reason to be afraid of releasing code with optimizations enabled. Enabling optimization can interfere with debugging which is a good reason to disable it for debug builds.

The optimizations shouldn't really break your code. There's a post here by Eric Lippert which explains what happens when you turn that flag on. The performance gain will vary from application to application, so you'll need to test it with your project to see if there are any noticeable differences (in terms of performance).

It is possible that some bugs will occur when running in release mode that do not otherwise occur. The infamous "non-volatile flag" comes to mind:

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

This happens because of compiler optimizations, as the flag variable gets cached by the core of thread t and thus it cannot see the updated value of flag.

Should optimisations introduce bugs? No.

Could optimisations introduce bugs? Maybe, nothing's perfect after all.

Could optimsations uncover bugs that were always in your code, but are hidden when they are turned off? Absolutely, happens quite a bit.

The important thing is to realise that it's a change. Just like you'd test if you'd done a lot of changes, you should test when you turn them off. If final-release will have them turned on, then final-test must have them turned on too.

Example wise i have a piece of code from some simulation parts of my master thesis. In which with the optimization flag turned on the code don't really break the program, but the pathfinder only performs one run and loops. (the recursive code traps itself in a loop on the pathfinder which it always breaks out of with the optimization flag turned off).

So yes it is possible for the optimization flag to make the software behave differently.

In C# the optimization should NEVER break your code.

Instead, with optimizations turned on the compiler produces more compact CIL when translating between C# and CIL.

I observed (and frankly it's interesting!) that the C# compilers from .NET < 2.0 (1.0 and 1.1) produced as good CIL WITHOUT optimizations as later C# compilers (2.0 and later) produce WITH optimizations.

.net compiler optimization could cause bugs. happend to me today. took me a few hours to nail it. the code is:

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

at some point, the list[i+1] is addressed as list[i] , as if both both point to the same item. this bug was so wierd. the code ran well at debug mode, and at release mode, but when I ran it out side visual studio, ex. from the .exe file, the code crashed. only turning off the compiler optimization fixed it.

In my case when I had the optimizations flag turned on it would not complete all the operations so there were measuring points missing in the final result so I simply turned the optimization flag off to fix the bug:

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<InspectionOperation> 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<MeasuringPoint> 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"),
                                        // e.g. "0001", "0002" and so on
                                        ItemCounter = itemCounter.ToString("D8"),
                                        // e.g. "00000001", "00000002" and so on
                                        MeasuringPointID = measuringPoint.MeasuringPointID,
                                        MeasuringPointDescription = measuringPoint.Description,
                                        Equipment = inspectionAsset.AssetID,
                                        Category = "P"
                                    };
                                DbContext.Entry(operationMeasuringPoint).State = EntityState.Added;
                                itemNumber++;
                                itemCounter++;
                            }
                        }
                    }
                }
            }
        }
    });
}

Thus I replaced the Parallel.Invoke call with this as well. FYI, this problem occurred using .NET Framework 4.7.

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

UPDATE:

OK, I've found that I was able to re-enable the optimization flag and use Parallel.Invoke if I remove the async Task from the method signature:

    private void ProcessPartialArrayInspectionOperations(IList<InspectionOperation> 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<MeasuringPoint> 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"),
                                    // e.g. "0001", "0002" and so on
                                    ItemCounter = itemCounter.ToString("D8"),
                                    // e.g. "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)
                        );

Alternatively, I think I could use Task.Run for each and then a await Task.WhenAll(t1, t2, t3); as explained here, but in this case I am not making explicit database calls so I don't think it applies to use Task.Run instead of Parallel.Invoke though this page does explain why my Parallel.Invoke was not completing: Parallel.Invoke does not wait for async methods to complete

For details, please see "Concurrency in C#" https://stephencleary.com/book/

I saw most that most upvoted answers claim that turning on optimization will not break your code. I disagree. I initially wrote this as a comment to DH's answer, but I would like to repeat it here.

On recent work on a very messy C# project, I advocated to turn on optimization. Things did break. Optimization broke loop code like this while (!someBool) , where the variable was being set by another thread or background worker. On one hand I blame the code (not so much,), on the other. I blame the optimizer for this decision, I would not have expected this behaviour from say, a Delphi optimizer. which is considered 'safe'. The solution was to add the volatile keyword against someBool to ensure it was not cached (into a register presumably) and never read again by the loop. volatile ensures the code reads the contents of someBool every iteration.

This unfortunate incident eroded any trust my boss had in the idea of turning on optimization, and it put enough doubt in my mind such that I can't guarantee that turning on optimization will not break any other piece of code, Thus. that project still has its optimization off. Better safe than sorry.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM