Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowed C# Compiler optimization on local variables and refetching value from memory

EDIT: I am asking what happens when two threads concurrently access the same data without proper synchronization (before this edit, that point was not expressed clearly).

I have a question about the optimizations that are performed by the C# compiler and by the JIT compiler.

Consider the following simplified example:

class Example {
    private Action _action;

    private void InvokeAction() {
        var local = this._action;
        if (local != null) {
            local();
        }
    }
}

Please ignore in the example that reading _action might yield a cached and outdated value as there is no volatile specifier nor any other sychronization. That is not the point :)

Is the compiler (or actually the jitter at runtime) allowed to optimize the assignment to the local variable out and instead reading _action from memory twice:

class Example {
    private Action _action;

    private void InvokeAction() {
        if (this._action != null) {
            this._action(); // might be set to null by an other thread.
        }
    }
}

which might throw a NullReferenceException when the field _action is set to null by a concurrent assignment.

Of course in this example such an "optimization" would not make any sense because it would be faster to store the value in a register and thus using the local variable. But in more complex cases, is there a guarantee that this works as expected without re-reading the value from memory?

like image 269
thaller Avatar asked Oct 05 '11 15:10

thaller


2 Answers

I will say (partially) the opposite of mgronber :-) Aaaah... In the end I'm saying the same things... Only that I'm quoting an article :-( I'll give him a +1.

It's a LEGAL optimization under the ECMA specifications, but it's an illegal optimization under the .NET >= 2.0 "specifications".

From the Understand the Impact of Low-Lock Techniques in Multithreaded Apps

Read here Strong Model 2: .NET Framework 2.0

Point 2:

Reads and writes cannot be introduced.

The explanation below:

The model does not allow reads to be introduced, however, because this would imply refetching a value from memory, and in low-lock code memory could be changing.

BUT note under in the same page, under Technique 1: Avoiding Locks on Some Reads

In systems using the ECMA model, there is an additional subtlety. Even if only one memory location is fetched into a local variable and that local is used multiple times, each use might have a different value! This is because the ECMA model lets the compiler eliminate the local variable and re-fetch the location on each use. If updates are happening concurrently, each fetch could have a different value. This behavior can be suppressed with volatile declarations, but the issue is easy to miss.

If you are writing under Mono, you should be advised that at least until 2008 it was working on the ECMA memory model (or so they wrote in their mailing list)

like image 72
xanatos Avatar answered Oct 12 '22 00:10

xanatos


It is legal optimization according to the memory model defined in the ECMA specification. If the _action were volatile, memory model would guarantee that the value is read only once and so this optimization could not happen.

However, I think that current Microsoft's CLR implementations do not optimize local variables away.

like image 43
mgronber Avatar answered Oct 12 '22 00:10

mgronber