C# 共享内存 - CPU 缓存的风险(非易失性读取)?
C# Shared Memory - risk of CPU caching (non-volatile reads)?
我想知道 C# 中共享内存的实现。 MemoryMappedViewAccessor 允许您从共享内存区域读取数据。
现在,memoryMappedViewAccessor 继承自 UnmanagedMemoryAccessor,它暴露了 ReadInt32() 等方法,其实现可以在这里看到 https://referencesource.microsoft.com/#mscorlib/system/io/unmanagedmemoryaccessor.cs,7632fe79d4a8ae4c 。原理上好像是用比较简单的unsafe指针arithmetic/casting,
pointer += (_offset + position);
result = *((Int32*)(pointer));
但是..(如何)保证 CPU 不会缓存这个值?
与此一样,您可以使用 "volatile" 标记变量以确保此行为,但在上述情况下如何进行管理,其中数据是通过指针通过不安全代码从内存中读取的。这会始终被视为易失性读取,还是不是易失性读取?
如果是后者,这是否意味着共享内存数据可能在 Microsoft 的实现中不同步 - 例如外部进程非常频繁地覆盖某个内存位置,并且 C# 代码非常频繁地读取它,冒着将值 CPU 缓存而不是每次都从内存中新读取的风险?
谢谢
这部分代码 *((Int32*)(pointer))
被编译为访问内存位置的机器指令。所以每次调用ReadInt32
时,都会访问指定的内存位置。如果 "is it guaranteed the CPU wouldn't cache this value?" 您指的是 CPU 缓存,那么硬件中实现的缓存一致性将确保访问最新值。否则,如果您指的是用于保存加载数据直到发出加载的指令退出的内部缓冲结构,那么一旦加载退出,任何其他对同一内存位置的后续加载将不得不向缓存发送另一个内存请求层次结构(内部缓冲区不再包含数据)。
也就是说,如果没有加载序列化指令(加载屏障) 它们之间。 我不认为 像 ReadInt32
这样的大函数会发生这种情况(还要考虑函数 ReadInt32
调用的所有代码)。即使在调用之间没有任何代码的情况下连续调用 ReadInt32
,两次连续的 *((Int32*)(pointer))
访问之间也会有数百条指令,具有各种依赖性,将两次读取结合起来的机会几乎为零x86 和 ARM 处理器。处理器会在看到第二个之前很久就退出第一个读取。请注意,两个连续的读取可以合并为一个内存请求。
我想知道 C# 中共享内存的实现。 MemoryMappedViewAccessor 允许您从共享内存区域读取数据。
现在,memoryMappedViewAccessor 继承自 UnmanagedMemoryAccessor,它暴露了 ReadInt32() 等方法,其实现可以在这里看到 https://referencesource.microsoft.com/#mscorlib/system/io/unmanagedmemoryaccessor.cs,7632fe79d4a8ae4c 。原理上好像是用比较简单的unsafe指针arithmetic/casting,
pointer += (_offset + position);
result = *((Int32*)(pointer));
但是..(如何)保证 CPU 不会缓存这个值? 与此一样,您可以使用 "volatile" 标记变量以确保此行为,但在上述情况下如何进行管理,其中数据是通过指针通过不安全代码从内存中读取的。这会始终被视为易失性读取,还是不是易失性读取?
如果是后者,这是否意味着共享内存数据可能在 Microsoft 的实现中不同步 - 例如外部进程非常频繁地覆盖某个内存位置,并且 C# 代码非常频繁地读取它,冒着将值 CPU 缓存而不是每次都从内存中新读取的风险?
谢谢
这部分代码 *((Int32*)(pointer))
被编译为访问内存位置的机器指令。所以每次调用ReadInt32
时,都会访问指定的内存位置。如果 "is it guaranteed the CPU wouldn't cache this value?" 您指的是 CPU 缓存,那么硬件中实现的缓存一致性将确保访问最新值。否则,如果您指的是用于保存加载数据直到发出加载的指令退出的内部缓冲结构,那么一旦加载退出,任何其他对同一内存位置的后续加载将不得不向缓存发送另一个内存请求层次结构(内部缓冲区不再包含数据)。
也就是说,如果没有加载序列化指令(加载屏障) 它们之间。 我不认为 像 ReadInt32
这样的大函数会发生这种情况(还要考虑函数 ReadInt32
调用的所有代码)。即使在调用之间没有任何代码的情况下连续调用 ReadInt32
,两次连续的 *((Int32*)(pointer))
访问之间也会有数百条指令,具有各种依赖性,将两次读取结合起来的机会几乎为零x86 和 ARM 处理器。处理器会在看到第二个之前很久就退出第一个读取。请注意,两个连续的读取可以合并为一个内存请求。