Simulating a double break in C #

I run on a 32-bit machine, and I can confirm that long values ​​can break using the following code snippet, which hits very quickly.

static void TestTearingLong() { System.Threading.Thread A = new System.Threading.Thread(ThreadA); A.Start(); System.Threading.Thread B = new System.Threading.Thread(ThreadB); B.Start(); } static ulong s_x; static void ThreadA() { int i = 0; while (true) { s_x = (i & 1) == 0 ? 0x0L : 0xaaaabbbbccccddddL; i++; } } static void ThreadB() { while (true) { ulong x = s_x; Debug.Assert(x == 0x0L || x == 0xaaaabbbbccccddddL); } } 

But when I try something similar with doubling, I can't tear. Does anyone know why? As far as I can judge by specification, only float assignment is atomic. An appointment to a double should have a risk of rupture.

  static double s_x; static void TestTearingDouble() { System.Threading.Thread A = new System.Threading.Thread(ThreadA); A.Start(); System.Threading.Thread B = new System.Threading.Thread(ThreadB); B.Start(); } static void ThreadA() { long i = 0; while (true) { s_x = ((i & 1) == 0) ? 0.0 : double.MaxValue; i++; if (i % 10000000 == 0) { Console.Out.WriteLine("i = " + i); } } } static void ThreadB() { while (true) { double x = s_x; System.Diagnostics.Debug.Assert(x == 0.0 || x == double.MaxValue); } } 
+17
multithreading c # double-precision atomicity
Jan 25 '12 at 18:52
source share
4 answers
 static double s_x; 

It is much harder to demonstrate the effect when you use dual. The CPU uses special instructions to load and store doubles, respectively, FLD and FSTP. This is much simpler since there is no single instruction that loads / saves a 64-bit integer in 32-bit mode. To observe it, you need the variable address to be inconsistent, so it crosses the line boundary of the processor cache.

This will never happen with the declaration you used, the JIT compiler ensures that the double is correctly aligned, stored at an address multiple of 8. You can save it in the class field, only the GS distributor aligns to 4 in 32-bit mode. But what the shit shoot.

The best way to do this is to intentionally mis-align the double with a pointer. Insecure the class of the program and make it look like this:

  static double* s_x; static void Main(string[] args) { var mem = Marshal.AllocCoTaskMem(100); s_x = (double*)((long)(mem) + 28); TestTearingDouble(); } ThreadA: *s_x = ((i & 1) == 0) ? 0.0 : double.MaxValue; ThreadB: double x = *s_x; 

This still does not guarantee good misalignment (hehe), since there is no way to precisely control where AllocCoTaskMem () will align the selection relative to the beginning of the processor cache line. And it depends on the cache associativity in your processor core (mine is Core i5). You will have to bother with the bias, I got the value 28 by experiment. The value should be divided by 4, but not by 8, in order to really mimic the behavior of the GC heap. Continue adding 8 to the value until you double it to move to the cache line and activate assert.

To make it less artificial, you have to write a program that stores a double field in the class and receives a garbage collector to move it from memory so that it is biased. It is difficult to come up with an example program that ensures that this happens.

Also note how your program can demonstrate a problem called fake sharing. Comment on the call to the Start () method for thread B and notice how the faster thread A works. You see the cost of a processor that supports a cache line that is consistent between processor cores. Sharing is intended here, as threads access the same variable. Real false sharing occurs when threads access different variables that are stored in the same cache line. Otherwise, why alignment matters, you can observe the gap twice when part of it is in one line of the cache and part of it is in another.

+10
Jan 29 '12 at 15:08
source share

Oddly enough, it depends on your processor. While doubles are not guaranteed so as not to tear, they will not be used in many modern processors. Try AMD Sempron if you want to tear in this situation.

EDIT: Learned that hard way a few years ago.

+11
Jan 25 '12 at 18:59
source share

While doing some digging, I found some interesting readings regarding floating point operations on x86 architectures:

According to Wikipedia , the x86 floating point block stores floating point values ​​in 80-bit registers:

[...] subsequent x86 processors then integrated this x87 functionality on a chip that made x87 instructions de facto an integral part of the x86 instruction set. Each x87 register, known as ST (0), through ST (7), has a width of 80 bits and stores floating-point numbers. The IEEE standard format is double extended precision.

Also this other SO question is related: Some precision issues and numerical values ​​with floating point

This may explain why, although 64-bit doubles, they work atomically.

0
Jan 29 '12 at 9:19
source share

Why is this topic and sample code here?

http://msdn.microsoft.com/en-us/magazine/cc817398.aspx

0
Jan 29 2018-12-12T00:
source share



All Articles