内存模型:防止存储释放和负载获取重新排序

Oma*_*riO 5 .net c# performance volatile memory-model

众所周知,与Java的易失性不同,.NET的版本允许使用来自另一个位置的以下易失性读取来重新排序易失性写入.当它是一个问题时 MemoryBarier,建议放在它们之间,或者Interlocked.Exchange可以用来代替volatile写.

它可以工作,但MemoryBarier在高度优化的无锁代码中使用时可能成为性能杀手.

我想了一下,想出了一个主意.我希望有人告诉我,我是否采取了正确的方式.

所以,这个想法如下:

我们希望防止这两种访问之间的重新排序:

 volatile1 write

 volatile2 read
Run Code Online (Sandbox Code Playgroud)

从.NET MM我们知道:

 1) writes to a variable cannot be reordered with  a  following read from 
    the same variable
 2) no volatile accesses can be eliminated
 3) no memory accesses can be reordered with a previous volatile read 
Run Code Online (Sandbox Code Playgroud)

为了防止写入和读取之间不必要的重新排序,我们从刚刚写入的变量中引入了一个虚拟易失性读取:

 A) volatile1 write
 B) volatile1 read [to a visible (accessible | potentially shared) location]
 C) volatile2 read
Run Code Online (Sandbox Code Playgroud)

在这种情况下,B不能用A重新排序,因为它们都访问相同的变量, C不能用B重新排序,因为两个易失性读取不能相互重新排序,并且传递C不能用A重新排序 .

问题是:

我对吗?这种虚拟易失性读取是否可以用作这种情况的轻量级内存屏障?

Oma*_*riO 2

我忘记将很快找到的答案发回给SO。迟到总比不到好..

事实证明,由于处理器(至少是 x86-x64 类型)优化内存访问的方式,这是不可能的。我在阅读有关英特尔处理器的手册时找到了答案。示例 8-5:“允许处理器内转发”看起来很可疑。谷歌搜索“存储缓冲区转发”可找到 Joe Duffy 的博客文章(第一篇第二篇- 请阅读它们)。

为了优化写入,处理器使用存储缓冲区(每个处理器的写入操作队列)。本地缓冲写入允许它进行下一步优化:满足对同一内存位置的先前缓冲写入的读取,并且该写入尚未离开处理器。该技术称为存储缓冲区转发(或存储到加载转发)。

在我们的例子中,最终结果是,由于B处的读取是从本地存储(存储缓冲区)满足的,因此它不被视为易失性读取,并且可以通过来自另一个内存位置 ( C )的进一步易失性读取进行重新排序。

这似乎违反了“易失性读取不会相互重新排序”的规则。是的,这是一种违规行为,但非常罕见且具有异国情调。为什么会发生这样的事?可能是因为在 .NET(及其 JIT 编译器)问世多年后,英特尔发布了第一份关于其处理器内存模型的正式文档。

所以答案是:不,虚拟读取 ( B ) 不会阻止AC之间的重新排序,并且不能用作轻量级内存屏障。