在英特尔架构上双读原子?

时间:2014-07-14 07:45:49

标签: c# .net intel

我的同事和我正在讨论使用C#.NET 4.0在英特尔架构上读取双精度的原子性。他认为我们应该使用Interlocked.Exchange方法写入double,但只是读取double值(在其他一些线程中)保证是原子的。我的论点是.NET不保证这种原子性,但他的论点是在英特尔架构上这是有保证的(可能不在AMDSPARC等等。

任何英特尔和.NET专家都会分享一些亮点吗?

Reader可以读取陈旧(上一个)值,但不是不正确的值(写入之前和之后的部分读取给出垃圾值)。

3 个答案:

答案 0 :(得分:23)

  

我的同事和我正在讨论使用C#.NET 4.0在英特尔架构上读取双精度的原子性。

英特尔保证,当与8字节边界对齐时,读取和写入时,8字节双精度是原子的。

C#不保证双精度对齐8字节边界。

  

他认为我们应该使用Interlocked.Exchange方法写入double,但只是读取double值(在其他一些线程中)保证是原子的。

你的同事并没有仔细考虑这个问题。相对于其他互锁操作,互锁操作仅是原子。使用当时某些的互锁操作没有任何意义;这就像说通过北方交叉口的交通不必服从红绿灯,因为通过西方交叉口的交通 服从交通信号灯。每个人都必须遵守灯光以避免碰撞;你做不到一半。

  

我的论点是.NET不保证这种原子性,但他的论点是在英特尔架构上这是有保证的(可能不在AMD,SPARC等)。

看,假设论证是正确的,但事实并非如此。我们应该在这里得出的结论是,通过做错而节省的几纳秒在某种程度上值得冒险吗?忘了互锁。 每次完全锁定。在线程之间共享内存时,唯一一次采取完全锁定的情况是,当您遇到已证实的性能问题时,实际上是由于锁的12纳秒开销。这是当12纳秒的惩罚是你的程序中最慢的,而且仍然是不可接受的,那就是你应该考虑使用低锁解决方案的那一天。你的程序中最慢的东西是12纳秒无竞争锁?没有?然后停止使用这个参数,并花费宝贵的时间使程序中的部分更多超过12纳秒。

  

Reader可以读取陈旧(上一个)值,但不是不正确的值(写入之前和之后的部分读取给出垃圾值)。

不要将原子性与波动性混为一谈。

互锁操作和锁定语句都会形成内存屏障,以确保读取或发布最新值。不需要普通的非易失性读或写操作;如果碰巧这样做,你很幸运。

如果您对这些问题感兴趣,我偶尔会询问的相关问题是在什么情况下可以省略对整数访问的锁定。我关于这个主题的文章是:

答案 1 :(得分:20)

是和否。在32位处理器上,它不保证原子,因为double大于本机的单词大小。在64位处理器上,正确对齐的访问是原子的。 64位CLI保证64位读取的所有内容都是原子的。因此,您需要为 x64 (而不是任何CPU )构建程序集。否则,如果你的程序集可以在32位上运行,最好使用 Interlocked ,请参阅Eric Lippert的 Atomicity, volatility and immutability are different, part two 。我认为您可以依赖Eric Lippert对Microsoft CLR的了解。

ECMA CLI标准也支持这一点,即使C#本身不能保证:

  

符合标准的CLI应保证对其的读写访问权限   正确对齐的内存位置不大于本机字大小   (native int类型的大小)是原子的(参见§I.12.6.2)

它还说,在操作是原子的处理器上, Interlocked 方法通常被编译为单个指令,因此在我的书中使用它没有性能损失。另一方面,如果不应该使用它可能会有更严重的惩罚。

另一个相关的Stack Overflow问题是 What operations are atomic in C#?

答案 2 :(得分:18)

如果在8字节地址边界上对齐,则在英特尔架构上读取或写入双精度是原子的。请参阅Is Updating double operation atomic

尽管在英特尔架构上的.NET代码中读取和写入双打可能实际上是原子的,但我不相信它,因为C#规范不保证它,请参阅此引用from an Answer by Eric Lippert

  

以下数据类型的读写是原子的:bool,char,   byte,sbyte,short,ushort,uint,int,float和reference类型。在   添加,读取和写入具有基础类型的枚举类型   以前的列表也是原子的。读写其他类型的,   包括long,ulong,double和decimal,以及用户定义的   类型,不保证是原子的。

使用Interlocked进行阅读和书写以确保安全。它保证了原子性。在默认情况下是原子的体系结构中,它不应产生任何开销。您需要使用Interlocked进行读取以及写入以确保不会读取部分写入的值(quote from InterLocked.Read() documentation):

  

Read方法和Increment,Decrement的64位重载,   并且Add方法仅在System.IntPtr的系统上才是真正原子的   是64位长。在其他系统上,这些方法是原子的   彼此尊重,但不尊重其他方式   访问数据。因此,要在32位系统上保持线程安全,任何   必须通过成员访问64位值   联锁班。