官术网_书友最值得收藏!

Memory model and compiler optimizations

Memory model and compiler optimizations are not directly related to concurrency, but they are very important concepts for anyone who creates concurrent code, shown as follows:

class Program
{
  bool _loop = true;

  static void Main(string[] args)
  {
    var p = new Program();

    Task.Run(() =>
    {
      Thread.Sleep(100);
      p._loop = false;
    });

    while (p._loop);
    //while (p._loop) { Console.Write(".");};

    Console.WriteLine("Exited the loop");
  }
}

If you compile this with the Release build configuration and JIT compiler optimizations enabled, the loop will usually hang on the x86 and x64 architectures. This happens because JIT optimizes the p._loop read and does something like this:

if(p._loop)
{
  while(true);
}

If there is something inside the while loop, JIT will probably not optimize this code in this way. Also, we may use the volatile keyword with the Boolean flag like this:

volatile bool _loop;

In this case, JIT will turn off this optimization as well. This is where we use a memory model, and it gets complicated here. Here is a quote from the C# language specification:

For non-volatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multi-threaded programs that access fields without synchronization such as that provided by the lock-statement. These optimizations can be performed by the compiler, by the run-time system, or by hardware. For volatile fields, such reordering optimizations are restricted:

  • A read of a volatile field is called a volatile read. A volatile read has "acquire semantics"; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence.
  • A write of a volatile field is called a volatile write. A volatile write has "release semantics"; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.

As we can see, there is nothing specifically stated here about compiler optimizations, but in fact JIT does not optimize volatile field read in this case.

So we can see a description in a specification, but how does this really work? Let's look at a volatile read example:

class VolatileRead
{
  int _x;
  volatile int _y;
  int _z;

  void Read()
  {
    int x = _x; // 1
    int y = _y; // 2 (volatile)
    int z = _z; // 3
  }
}

The possible reordering options would be 1, 2, 3 (original); 2, 1, 3; and 2, 3, 1. This can be imagined as a one-way fence that allows the preceding operation to pass through, but does not allow subsequent operations. So this is called the acquire fence.

Volatile writes look pretty similar. Consider the following code snippet:

class VolatileWrite
{
  int _x;
  volatile int _y;
  int _z;

  void Read()
  {
    _x = 1; // 1
    _y = 2; // 2 (volatile)
    _z = 3; // 3
  }
}

Possible options here are 1, 2, 3 (original); 1, 3, 2; and 3, 1, 2. This is the release fence, which allows the reordering of only subsequent read or write operations but does not allow the preceding write operation. We have the Thread.VolatileRead and Thread.VolatileWrite methods that do the same thing explicitly. There is the Thread.MemoryBarrier (memory barrier) method as well, which allows us to use a full fence when we do not let through any operations.

I would like to mention that we are now on less certain ground. Different memory models on different architectures can be confusing, and code without volatile can perfectly work on x86 and amd64. However, if you are using shared data, please be aware of possible reordering and non-reordering optimizations and choose the appropriate behavior.

Note

Please be aware that making a field volatile means that all the read and write operations will have slightly lower performance and they will have the code in common, since some possible optimizations will be ignored.

主站蜘蛛池模板: 广元市| 加查县| 温宿县| 偃师市| 正宁县| 宜兰县| 正镶白旗| 阳信县| 自贡市| 常山县| 乐清市| 古田县| 长春市| 葵青区| 保靖县| 芜湖市| 新晃| 巴青县| 卫辉市| 清苑县| 台中县| 乐安县| 改则县| 桐庐县| 浏阳市| 鸡东县| 类乌齐县| 建宁县| 柯坪县| 潜山县| 会同县| 仲巴县| 庄河市| 临湘市| 揭东县| 通江县| 特克斯县| 孝昌县| 蓝田县| 上饶市| 铅山县|