这是安全的吗?
只要您的数据修改通过关键部分、锁或其他方式得到了安全和保护,就可以完全放心地进行硬件访问。
线程A是否真正更新了数组还是只是缓存中的副本?
只是缓存中的副本。现在大多数缓存都是写回的,并且只有当缓存行被驱逐时才将数据写回内存,如果已经被修改。这在多核上下文中大大提高了内存带宽。
但是所有的操作都好像内存已经被更新了。
对于共享内存处理器,通常有缓存一致性协议(除了某些用于实时应用程序的处理器)。这些协议的基本思想是为每个缓存行关联一个状态。状态描述了不同处理器缓存中该行的信息。例如,这些状态指示该行是否仅存在于当前缓存中,或者由多个缓存共享,在与内存同步,无效等。请参阅流行的MESI缓存一致性协议的
this description。
当一个缓存行被写入并且在另一个处理器中也存在时会发生什么呢?由于状态的存在,缓存知道其他一个或多个处理器也拥有该行的副本,并将发送一个无效信号。该行将在其他缓存中失效,当它们想要读取或写入它时,它们必须重新加载其内容。实际上,为了限制内存访问,这个重新加载将由具有有效副本的缓存提供。
这样,虽然数据只写入了缓存,但行为类似于数据已经写入内存的情况。
但是,尽管硬件在功能上保证了传输的正确性,但必须考虑到缓存的存在,以避免性能下降。假设缓存A正在更新一行数据,而缓存B正在读取它。每当缓存A写入时,缓存B中的行就会失效。每当缓存B想要读取它时,如果该行已失效,则必须从缓存A中获取。这可能导致行在缓存之间的多次传输,从而使内存系统效率低下。
因此,关于您的例子,使用缓存信息来改进发送者和接收者之间的交换可能不是一个好主意,您应该选择其他方法。
例如,如果您使用带有64字节缓存行的Pentium处理器,则应将X声明为:
_Alignas(64) float X[100];
这样,
X
的起始地址将是 64 的倍数,并适合于缓存行边界。自 C17 起,
_Alignas
限定符存在,通过包含 stdalign.h,您还可以使用类似的
alignas(64)
。在 C17 之前,大多数编译器都有一些扩展来实现对齐放置。
当然,您应该告知进程 B 只有在写入完整的 64 字节行(16 个浮点数)后才读取数据。
这样,当线程 B 访问数据时,缓存行将不再被线程 A 修改,只会发生一次缓存 A 和 B 之间的初始传输。这种减少缓存之间传输次数的方法可能会对程序的性能产生显著影响。