Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET JIT compiler volatile optimizations

https://msdn.microsoft.com/en-us/magazine/jj883956.aspx

Consider the polling loop pattern:

private bool _flag = true; 
public void Run() 
{
    // Set _flag to false on another thread
    new Thread(() => { _flag = false; }).Start();
    // Poll the _flag field until it is set to false
    while (_flag) ;
    // The loop might never terminate! 
} 

In this case, the .NET 4.5 JIT compiler might rewrite the loop like this:

if (_flag) { while (true); } 

In the single-threaded case, this transformation is entirely legal and, in general, hoisting a read out of a loop is an excellent optimization. However, if the _flag is set to false on another thread, the optimization can cause a hang.

Note that if the _flag field were volatile, the JIT compiler would not hoist the read out of the loop. (See the “Polling Loop” section in the December article for a more detailed explanation of this pattern.)

Will the JIT compiler still optimize the code as shown above if I lock _flag or will only making it volatile stop the optimization?

Eric Lippert has the following to say about volatile:

Frankly, I discourage you from ever making a volatile field. Volatile fields are a sign that you are doing something downright crazy: you're attempting to read and write the same value on two different threads without putting a lock in place. Locks guarantee that memory read or modified inside the lock is observed to be consistent, locks guarantee that only one thread accesses a given chunk of memory at a time, and so on. The number of situations in which a lock is too slow is very small, and the probability that you are going to get the code wrong because you don't understand the exact memory model is very large. I don't attempt to write any low-lock code except for the most trivial usages of Interlocked operations. I leave the usage of "volatile" to real experts.

To summarize: Who ensures that the optimization mentioned above doesn't destroy my code? Only volatile? Also the lock statement? Or something else?

As Eric Lippert discourages you from using volatile there must be something else?


Downvoters: I appreciate every feedback to the question. Especially if you downvoted it I'd like to hear why you think this is a bad question.


A bool variable is not a thread synchronization primitive: The question is meant as a generell question. When will the compiler not do the optimizion?


Dupilcate: This question is explicitly about optimizations. The one you linked doesn't mention optimizations.

like image 622
NtFreX Avatar asked Oct 01 '18 12:10

NtFreX


1 Answers

Let's answer the question that was asked:

Will the JIT compiler still optimize the code as shown above if I lock _flag or will only making it volatile stop the optimization?

OK, let's not answer the question that was asked, because that question is too complicated. Let's break it down into a series of less complicated questions.

Will the JIT compiler still optimize the code as shown above if I lock _flag?

Short answer: lock gives a stronger guarantee than volatile, so no, the jitter will not be permitted to lift the read out of the loop if there is a lock around the read of _flag. Of course the lock also has to be around the write. Locks only work if you use them everywhere.

private bool _flag = true; 
private object _flagLock = new object();
public void Run() 
{
  new Thread(() => { lock(_flaglock) _flag = false; }).Start();
  while (true)
    lock (_flaglock)
      if (!_flag)
        break;
} 

(And of course, I note that this is an insanely bad way to wait for one thread to signal another. Don't ever sit in a tight loop polling a flag! Use a wait handle like a sensible person.)

You said locks were stronger than volatiles; what does that mean?

Reads to volatiles prevent certain operations from being moved around in time. Writes to volatiles prevent certain operations from being moved around in time. Locks prevent more operations from being moved around in time. These prevention semantics are called "memory fences" -- basically, volatiles introduce a half fence, locks introduce a full fence.

Read the C# specification section on special side effects for the details.

As always, I'll remind you that volatiles do not give you global freshness guarantees. There is no such thing in multithreaded C# programming as "the latest" value of a variable, and so volatile reads do not give you "the latest" value, because it doesn't exist. The idea that there is a "latest" value implies that reads and writes are always observed to have a globally consistent ordering in time, and that is false. Threads can still disagree on the order of volatile reads and writes.

Locks prevent this optimization; volatiles prevent this optimization. Are those the only thing which prevents the optimization?

No. You can also use Interlocked operations, or you can introduce memory fences explicitly.

Do I understand enough of this to use volatile correctly?

Nope.

What should I do?

Don't write multithreaded programs in the first place. Multiple threads of control in one program is a bad idea.

If you must, don't share memory across threads. Use threads as low-cost processes, and only use them when you have an idle CPU that could do a CPU-intensive task. Use single threaded asynchrony for all I/O operations.

If you must share memory across threads, use the highest level programming construct available to you, not the lowest level. Use a CancellationToken to represent an operation being cancelled elsewhere in an asynchronous workflow.

like image 148
Eric Lippert Avatar answered Sep 21 '22 12:09

Eric Lippert