模拟在C#中撕掉一个双

我正在使用32位计算机运行,并且我能够确认使用以下代码片段可以快速查看长值。

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); } } 

但是当我尝试与双打类似的东西时,我无法得到任何撕裂。 有谁知道为什么? 据我所知,只有浮点数的赋值才是primefaces的。 分配给双人应该有撕裂的风险。

  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); } } 

 static double s_x; 

使用双精度表演效果要困难得多。 CPU使用专用指令来加载和存储双重FLD和FSTP。 由于没有单个指令在32位模式下加载/存储64位整数,所以很容易。 要观察它,你需要使变量的地址不对齐,以便它跨越cpu缓存行边界。

使用的声明永远不会发生这种情况,JIT编译器确保双精度正确对齐,存储在8的倍数地址。您可以将它存储在类的字段中,GC分配器仅对齐到4 32位模式。 但那是一个废话。

最好的方法是通过使用指针故意错误对齐double。 将不安全的东西放在Program类的前面,使它看起来像这样:

  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; 

这仍然不能保证良好的错位(hehe),因为无法准确控制AllocCoTaskMem()将相对于cpu缓存行的开头对齐分配的位置。 它取决于你的cpu核心中的缓存关联性(我的是Core i5)。 你必须修补偏移量,我通过实验得到了值28。 该值应该可被4整除,但不能被8整除,以真正模拟GC堆行为。 继续向该值添加8,直到您获得双倍跨越缓存行并触发断言。

为了减少人工,你必须编写一个程序来存储类的字段中的double,并让垃圾收集器在内存中移动它以使它不对齐。 有点难以想出一个确保这种情况发生的示例程序。

另请注意您的程序如何演示称为错误共享的问题。 注释掉线程B的Start()方法调用,并注意线程A的运行速度。 你看到cpu的成本使得cpu核心之间的缓存线保持一致。 由于线程访问相同的变量,因此这里是共享。 当线程访问存储在同一缓存行中的不同变量时,会发生真正的错误共享。 这就是为什么对齐很重要的原因,只有当它的一部分在一个缓存行中而另一部分在另一个缓存行中时,你才能观察到双重撕裂。

听起来很奇怪,这取决于你的CPU。 虽然双打不能保证不撕裂,但它们不会在许多当前的处理器上。 如果你想在这种情况下撕裂,试试AMD Sempron。

编辑:了解几年前的困难。

做了一些挖掘,我发现了一些关于x86架构浮点运算的有趣读物:

根据Wikipedia ,x86浮点单元在80位寄存器中存储浮点值:

[…]随后的x86处理器随后将x87function集成在芯片上,使x87指令成为x86指令集事实上不可或缺的一部分。 每个x87寄存器(称为ST(0)到ST(7))为80位宽,并以IEEE浮点标准双扩展精度格式存储数字。

此另一个SO问题也是相关的: 一些浮点精度和数值限制问题

这可以解释为什么,虽然双精度是64位,但它们是primefaces操作的。

有关它的价值,可以在此处找到此主题和代码示例。

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