C#共享内存 - CPU缓存的风险(非易失性读取)?

时间:2018-04-11 13:47:29

标签: c# shared-memory compiler-optimization cpu-architecture memory-barriers

我想知道C#中共享内存的实现。 MemoryMappedViewAccessor允许您从共享内存区域读取数据。

现在,memoryMappedViewAccessor继承自UnmanagedMemoryAccessor,它取代了ReadInt32()这样的方法,其实现可以在https://referencesource.microsoft.com/#mscorlib/system/io/unmanagedmemoryaccessor.cs,7632fe79d4a8ae4c看到。原则上,它似乎使用相对简单的不安全指针算术/转换,

 pointer += (_offset + position);
 result = *((Int32*)(pointer));

但..(如何)保证CPU不会缓存这个值? 在中,您可以使用" volatile"标记变量。确保这种行为,但在上述案例中如何管理,其中数据是通过指针通过不安全的代码从内存中读取的。这总是被视为易失性读数,还是不易变?

如果是后者,那么这并不意味着共享内存数据可能会在Microsoft的实现中失去同步 - 例如一个外部进程非常频繁地覆盖某个内存位置,而C#代码会非常频繁地读取这个内容,冒着CPU缓存而不是每次从内存重新读取的风险?

由于

1 个答案:

答案 0 :(得分:2)

代码*((Int32*)(pointer))的这一部分被编译为访问内存位置的机器指令。因此,每次调用ReadInt32时,都将访问指定的内存位置。如果“是否保证CPU不会缓存此值?”您指的是CPU缓存,然后在硬件中实现的缓存一致性将确保访问最新值。否则,如果您指的是用于保存已加载数据的内部缓冲结构,直到发出加载的指令退出,那么一旦加载退出,对同一内存位置的任何其他后续加载将不得不向缓存发送另一个内存请求层次结构(内部缓冲区不再包含数据)。

也就是说,如果在它们之间没有负载序列化指令(负载屏障),那么在指令序列中足够接近且访问相同存储器位置的两个负载可以组合(假设CPU支持这种技术)。 我不认为会出现像ReadInt32这样的大型函数(同时考虑函数ReadInt32调用的所有代码)。即使在调用之间没有任何代码的情况下连续调用ReadInt32,在具有各种依赖性的两个连续*((Int32*)(pointer))访问之间将存在数百条指令,组合两个读取的几率实际上为零。 x86和ARM处理器。处理器在看到第二个读取之前很久就会退出第一个读取。请注意,两个连续读取可以组合成一个内存请求。