当核心在其L1缓存中写入而另一个核心在其L1中具有相同的行时会发生什么?
让我们说一下英特尔Skylake CPU.
缓存系统如何保持一致性?它是否实时更新,是否会阻止其中一个核心?使用两个内核连续写入相同缓存行的性能成本是多少?
一般现代的CPU使用的一些变型1中的MESI协议以保持高速缓存一致性.
在L1写入的场景中,细节取决于缓存行的现有状态:缓存行是否已经存在于写入核心的缓存中?在另一个核心中,缓存行处于什么状态,例如,它是否被修改过?
让我们来看一下这个简单的情况,即该行不在写入核心(C1)中,并且它在另一个核心(C2)中处于"独占"状态.在已知写入地址的位置,C1将使用线路地址向"总线"发出RFO(所有权请求)事务,其他内核将监听总线并注意事务.具有该行的另一个核心然后将其行从独占状态转换为无效状态,并且该行的值的值将被提供给请求核心,这将使其处于修改状态,此时写入可以继续
注意,此时,从写入核心向该行的进一步写入很快进行,因为它处于M状态,这意味着不需要发生总线事务.在线路被驱逐或某些其他核心请求访问之前将是这种情况.
现在,在实际实现中有许多其他细节,这些细节在上面没有涉及,甚至在协议的维基百科描述中也没有涉及.
例如,基本模型涉及每个CPU一个私有缓存和共享主内存.在这个模型中,核心C2通常会将共享线路的值提供给总线,即使它没有对其进行修改,因为这比等待从主存储器读取值要快得多.但是,在最近的所有x86实现中,共享的最后一级L3缓存位于所有私有L2和L1缓存以及主内存之间.此缓存通常具有包容性,因此它可以直接向C1提供值,而无需从C2执行缓存到缓存传输.此外,具有该共享高速缓存意味着每个CPU实际上可能不需要窥探"总线",因为可以首先查询L3高速缓存以确定哪个(如果有的话)核实际上具有该线.然后,只有具有该行的核心才会被要求进行状态转换.一种推模型而不是拉.
尽管有所有这些实现细节,但基本原理是相同的:每个缓存行都有一些"每个核心"状态(即使这个状态可能存储或复制在像LLC这样的某个中心位置),并且这个状态原子地经历逻辑转换以确保缓存行始终保持一致.
鉴于背景,以下是最后两个子问题的具体答案:
它是否实时更新,是否会阻止其中一个核心?
任何现代核心都将实时执行此操作,并且还可以并行执行不同的缓存行.这并不意味着它是免费的!例如,在上面的描述中,C1的写入被停止,直到高速缓存一致性协议完成,这可能是几十个周期.与普通写入相比,只需几个周期.还存在带宽问题:用于实现协议的请求和响应使用可能具有最大吞吐量的共享资源; 如果一致性事务的速率超过某个限制,则即使它们是独立的,所有请求也可能会减慢.
在过去,当真正有共享公交车时,在某些情况下可能会有一些部分"停止世界"的行为.例如,lockx86原子指令的前缀显然是根据lockCPU在进行原子事务时在总线上断言的信号命名的.在整个期间,其他CPU无法完全使用总线(但可能他们仍然可以继续使用CPU本地指令).
使用两个内核连续写入相同缓存行的性能成本是多少?
成本非常高,因为如上所述,线路将在两个核心之间连续乒乓(在所描述的过程结束时,只需反转C1和C2的角色并重新启动).确切的细节因CPU甚至平台而有很大不同(例如,2插槽配置会大大改变这种行为),但基本上可能会考虑每次写入10次循环与1次非共享输出的罚分.每个周期写.
您可以在此问题的答案中找到一些特定的数字,其中包括"同一物理核心上的两个线程"案例和"单独核心"案例.
如果您想了解有关特定性能方案的更多详细信息,您可能应该提出一个单独的特定问题,列出您感兴趣的特定行为.
1上MESI的变化常常引入新的状态,如"拥有"状态中MOESI或"转发"状态中MESIF.这个想法通常是为了使某些转换或使用模式比普通的MESI协议更有效,但基本思想大致相同.