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

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.

主站蜘蛛池模板: 太原市| 思茅市| 奉化市| 饶河县| 勐海县| 龙口市| 尉犁县| 逊克县| 内江市| 高雄市| 武汉市| 开江县| 都兰县| 清苑县| 禹州市| 揭东县| 崇信县| 霍林郭勒市| 昌图县| 建湖县| 含山县| 丹江口市| 高州市| 长葛市| 南汇区| 灵石县| 门头沟区| 运城市| 灵川县| 惠州市| 大丰市| 长武县| 文登市| 罗甸县| 莲花县| 彰化县| 江孜县| 武宁县| 长武县| 铁岭市| 平顶山市|