高速缓存一致性操作期间处理器是否停顿

时间:2019-04-01 21:45:24

标签: multithreading caching cpu-architecture cpu-cache

让我们假设变量a = 0

Processor1: a = 1
Processor2: print(a)

Processor1首先执行它的指令,然后在下一个周期中,processor2读取变量以打印它。也是如此:

  1. processor2将暂停,直到缓存一致性操作完成并且将打印1

    P1:   |--a=1--|---cache--coherence---|----------------
    P2:   ------|stalls due to coherence-|--print(a=1)---|
    time: ----------------------------------------------->
    
  2. processor2将在高速缓存一致性操作完成之前运行,并且在此之前将具有陈旧的内存视图。这样它将打印0?

    P1:   |--a=1--|---cache--coherence---|
    P2:   ----------|---print(a=0)---|----
    time: ------------------------------->
    

    换句话说,在高速缓存一致性操作完成之前,处理器能否拥有陈旧的内存视图?

2 个答案:

答案 0 :(得分:5)

所有现代ISA都使用MESI(作为其变体)来实现缓存一致性。这样可以在所有处理器都拥有的内存共享视图(通过缓存)中始终保持一致性。

例如,参见Can I force cache coherency on a multicore x86 CPU?,这是一个常见的误解,即存储进入缓存,而其他核心仍具有缓存行的旧副本,然后必须发生“缓存一致性”。

但是事实并非如此:要修改缓存行,CPU需要对该行具有排他所有权(MESI的“已修改”或“排他”状态)。只有在收到对“读取所有权”的响应(如果该响应之前处于共享或无效状态)后,该响应会使高速缓存行的所有其他副本无效,这才可能实现。例如,请参见Will two atomic writes to different locations in different threads always be seen in the same order by other threads?


但是,内存模型允许对存储和加载进行本地重新排序。顺序一致性太慢,因此CPU始终至少允许StoreLoad重新排序。另请参阅Is mov + mfence safe on NUMA?,以获取有关x86上使用的TSO(总存储订单)内存模型的许多详细信息。许多其他的ISA使用甚至更弱的模型。

对于这种情况下的非同步读取器,如果两者都运行在单独的内核上,则存在三种可能性

  • load(a)发生在缓存行无效之前的核心#2上,因此它读取旧值,从而有效地发生在a=1以全局顺序存储之前。 负载可以达到L1d缓存
  • load(a)发生在core#1将存储提交到其L1d缓存后,尚未回写。 Core#2的读取请求触发Core#2回写以共享共享级别的缓存(例如L3),并将该行置于Shared状态。 肯定会在L1d中丢失负载
  • load(a)在写回内存或至少已经发生L3之后发生,因此它不必等待core#1写回。 负载将在L1d中丢失,除非硬件预取由于某种原因将其重新带回。但是通常,这仅是顺序访问(例如,对数组)的一部分发生的。

因此,是的,如果另一个内核在尝试加载该内核之前已经将其提交给缓存,则该加载将停止。

另请参阅Size of store buffers on Intel hardware? What exactly is a store buffer?,详细了解存储缓冲区对所有事物的影响,包括内存重新排序。

这里没有关系,因为您有一个只写的生产者和一个只读的使用者。生产者核心无需等待其商店在全球范围内可见就可以继续操作,它可以在全局可见之前立即看到自己的商店。当您让每个线程查看由另一个线程完成的存储时,这很重要。那么您需要障碍或顺序一致的原子操作(编译器使用障碍来实现)。 请参阅https://preshing.com/20120515/memory-reordering-caught-in-the-act

另请参阅 Can num++ be atomic for 'int num'?了解原子RMW如何与MESI配合使用,这对于理解该概念很有帮助。 (例如,原子RMW可以通过将内核挂接到处于“修改”状态的高速缓存行上,并延迟对RFO的响应或请求共享它,直到RMW的写入部分已提交来起作用。)

答案 1 :(得分:3)

在此示例中,对a的读取和写入访问是并发的,并且可以按任何顺序完成。这取决于哪个处理器首先访问该线路。高速缓存一致性仅保证同一一致性域中的所有处理器都同意存储在所有高速缓存行中的值。因此最终结果不能是a有两个副本,一个副本的值为0,另一个副本为1。

如果要确保处理器2看到处理器1写入的值,则必须使用同步机制。一种简单但效率低下的方法是:

Processor1: 
a = 1
rel = 1

Processor2: 
while(rel != 1){ }
print(a)

如果满足以下属性,这将起作用:

  • 存储在编译器级别和ISA级别按顺序完成。
  • 在编译器级别和ISA级别按顺序完成加载。

满足这些属性的ISA的示例是x86-64,假设rel不大于8字节并且自然对齐并且没有从WC内存类型的内存区域分配所有变量。 / p>


关于您对问题的更新。如果处理器1在处理器2读取该行之前已获得该行的所有权,则处理器2可能会停顿,直到处理器1完成其写操作并获取更新的行。如果处理器1检测到来自另一个处理器的对该行的读取请求,则可以决定在写入之前放弃该行的所有权。但是无论发生什么情况,两次访问都会以一定顺序完成。