内存栅栏:软件高手的硬件观(一)

来源:互联网 发布:淘宝选品数据分析 编辑:程序博客网 时间:2024/06/05 05:37

因翻译水平有限,如有不妥,敬请指正和谅解!
原文下载地址:
http://download.csdn.net/download/programresearch/9829674

是什么让疯狂的CPU设计者们强加内存栅栏给无辜的(可怜而毫不知情的)SMP(对称多处理器系统)软件设计者?
简而言之,因为内存引用的重排序可以达到更好的性能.因此在像同步原语这样的事上,需要用内存栅栏来强制排序.同步原语操作依赖于有序的内存引用.

获得此问题更详细的答案,要求很好的理解CPU高速缓存是如何工作的,并且特别是是什么要求使得高速缓存工作良好.后面章节:
1.介绍高速缓存的结构
2.描述高速缓存一致性协议如何确保CPU协商内存中每个位置的值,并且最终一致.
3.概述存储缓冲和无效队列如何辅助高速缓存和高速缓存一致性协议实现高性能.
将看到内存栅栏在要求好的性能和可扩展性时有一个不可避免的问题.问题源于事实,CPU数量级的快于它们之间连接的和它们试图访问的内存.

1.高速缓存结构

现在的CPU比现在的内存系统快得多.一个2006年的CPU可能执行十条指令每纳秒,但将需要多个十纳秒从主内存去获取数据项.此速度上的差距(超过两个数量级),导致很多兆(M)字节的高速缓存存在于现在的CPU上.这些缓存关联CPU如图1所示,并且通常在几个周期内访问.
现代计算机系统缓存结构

数据流在CPU高速缓存和内存之间按固定长度分块的称为”缓存行”,它的大小一般是2的指数倍.范围从16到256字节.当一个给定的数据项首次由给定的CPU访问,它将不存在于CPU缓存中,意味着发生了”缓存未命中”(或,更具体的是”启动”或”预热”缓存未命中).缓存未命中,意味着当数据从内存中获取时,CPU将必须等待(或被”停滞”)数百个周期.而后,项将被装载到CPU缓存中,因此后续访问将在缓存中找到它,并因而全速运行.

在一段时间后,CPU缓存将被填满,并且后续未命中将需要从缓存中弹出一些项,为了腾出空间用于新获取的项.这样的缓存未命中,被称为”容量未命中”,因为它由缓存的容量限制导致.然而,很多缓存能被强制弹出旧项,用于腾出空间为了新项,即使当它们还没有满时.这由于事实,大的缓存实现按有固定大小的散列桶(或”集合”,如CPU设计者所称呼它们)的硬件散列表,而不是链接,如图2显示.
CPU缓存结构

此缓存有16个”集合”和两”路”,总共32”行”,每个入口包含单一的256字节”缓存行”,即256字节对齐的内存块.此缓存行是大块尺寸的一小部分,但使十六进制算法更简单.用硬件的说法,此为两路集合相关缓存,并且模拟为十六桶的软件散列表,这儿各个桶的散列链被限制为最多两个元素.大小(在此例子中32缓存行)和相关性(在此例中为2)被统称为缓存的”几何结构”.由于此缓存在硬件中实现,散列函数极其简单:从内存地址提取4位.

 在图2中,每个盒对应一个缓存入口,它能包含256字节缓存行.然而,一个缓存入口可为空,如图中空盒所指示.其余的盒子用所包含的缓存行的内存地址标记.由于缓存行必须是256字节对其,各地址的低8位为0,并且硬件散列函数选择意味着接下来的高4位匹配散列函数. 图中描述的情况可能出现,如果程序代码位于地址0x43210E00到0x43210EFF,并且此程序顺序访问数据从0x12345000到0x12345EFF.假设程序现在访问地址0x12345F00.此地址散列到行0xF,并且此行的两路都是空,因此对应的256字节行能被容纳.如果程序访问地址0x1233000,此散列到行0x0,对应的256字节缓存行能被容纳到路1.然而,如果程序访问地址0x1233E00,它的散列是0xE行,一个存在行必须从缓存弹出,去腾出空间用于新的缓存行.如果这个弹出行,在之后被访问,将导致一个缓存未命中.这个缓存未命中被称为”关联性未命中”.

到目前为止,只考虑了CPU在哪儿读数据项的情况.当它写时发生了什么呢?因为让所有的CPU同意给定数据项的值是很重要的,在给定CPU写数据项之前,它必须首先使此项从其他CPU缓存中被移除,或”无效”.一旦此无效完成,CPU可以安全地修改数据项.如果数据项存在于此CPU的缓存中,但是为只读,此过程被称为”写未命中”.一旦一个给定CPU完成使其他CPU缓存数据项无效,此CPU可能重复地写(和读)数据项.

 显然,必须非常小心的确保所有CPU保持一致的数据视图. 随着所有这些的获取,无效和写,非常容易去想象数据被丢失,或(可能更糟糕)不同的CPU在它们各自的缓存中对相同的数据项有冲突值.这些问题由”缓存一致性协议”所阻止,在下一章节描述.

2.缓存一致性协议

缓存一致性协议管理缓存行状态,因此用于防止不一致或数据丢失.这些协议可能相当复杂,有数十种状态,但出于我们的目的,仅需要关注4状态的MESI缓存一致性协议.

2.1 MESI状态

MESI代表”修改”,”独占”,”共享”和”无效”,通过使用此协议,给定的缓存行可处在四种状态.使用此协议的缓存要维护两位状态”标志”在各缓存行中,除了行的物理地址和数据.

在”修改”状态下的行,已经被最近的内存存储所对应的CPU”修改”,并且对应的内存不保证出现在任何其他CPU缓存中.缓存行在”修改”状态,因而能说被CPU”拥有”.因为此缓存持有仅有的最新数据副本,此缓存最终负责写回内存或移交到其他缓存,并且必须在重用此行持有其他数据前完成.

“独占”状态非常类似于”修改”状态,除了缓存行没有被对应的CPU修改.这也意味着,驻留在内存中的缓存行数据的副本是最新的.因而,由于CPU可以在任何时候存储此行,不用询问其他CPU,在”独占”状态下的行,也能说由对应CPU所拥有.也就是说,因为在内存中对应的值是最新的,此缓存可丢弃此数据,而不写回到内存或转移它到其他CPU.

“共享”状态下的行可能被复制至少一次在其他CPU的缓存中.与”独立”状态一致,因为在内存中对应的值是最新的,此缓存可丢弃此数据,而不写回到内存或转移它到其他CPU.

在”无效”状态下的行是空的,也就是说,它不持有数据.当新数据进入缓存,如果可能它被放置到”无效”状态的缓存行.此方法是首选,因为从任何其他状态替换一行,可能导致将在未来引用的替换行的高昂的缓存丢失.

由于所有CPU必须维护在缓存行中所携带数据的一致性视图,缓存一致性协议提供消息,协调缓存行在系统中移动.

2.2 MESI协议消息

在先前段中的许多转换描述要求CPU之间的通信,如果CPU在单共享总线上,如下信息足以:
读: “读”消息包含缓存行的物理地址被读取

读响应: “读响应”消息包含由一个早期的”读”消息请求的数据.”读响应”消息可能由内存或其他缓存之一提供.例如,一个缓存拥有数据在”修改”状态,此缓存必须提供”读响应”消息.

无效: “无效”消息包含要被无效的缓存行的物理地址.所有其他缓存必须从它们的缓存中移除对应的数据并响应.

无效确认: CPU接收到一个”无效”消息必须响应一个”无效确认”消息,在从它的缓存中移除指定数据后.

读取无效: “读取无效”消息,包含要读取缓存行的物理地址,而同时指导其他缓存去移除数据.因此,它结合了”读”和”无效”,如它的名字所表示”读取无效”消息要求同时有一个”读响应”和一个”无效确认”集合消息回复.

写回: “写回”消息包含要写回到内存的地址和数据(并可能用此方法探测其他CPU的缓存).此消息允许缓存根据需要弹出在”修改”状态的行,腾出空间用于其他数据.

有趣的是,共享内存多处理系统实际上是封装下的消息传递机.此意味着使用分布式共享内存的SMP机器集群使用消息传递去实现共享内存在两个不同级别的系统结构上.

快速测验1: 如果两个CPU试图同时使相同的缓存行无效,将发生什么?

快速测验2: 当一个”无效”消息出现在大型的多处理器中,每个CPU必须给出一个”无效确认”响应.”无效确认”响应结果风暴是否会使系统总线饱和?

快速测验3: 如果SMP及其实际使用消息,何苦还要用SMP?

2.3 MESI状态图

给定的缓存行状态随着协议消息的发送和接收而改变,如图3所示:
MESI缓存一致性状态图

此图中的转换过程如下:
转换(a): 缓存行被写回到内存,但是CPU保存它在缓存中,并进一步保存修改它的权利.此转换要求一个”写回”消息

转换(b): CPU写它已经独占访问的缓存行.此转换不能要求任何消息被发送或被接收.

转换(c): CPU收到针对它修改的缓存行的”读无效”消息.CPU必须无效它的本地副本,然后同时响应”读响应”和”无效确认”消息,都发送数据去请求CPU,并指示不再有本地副本.

转换(d): CPU做一个原子地”读-修改-写”操作对一个不在缓存中的数据项.它发送一个”读无效”,接收数据通过”读响应”.CPU完成转换一旦它收到全部的”无效确认”响应消息.

转换(e): CPU做原子的读-修改-写操作在一个数据项上,它先前是只读的,在缓存中,它必须传递一个”无效”消息,并且必须在完成转换前,等待一个全部的”无效确认”响应集合.

转换(f): 一些其他CPU读缓存行,这些是由此CPU的缓存提供.它维护一个只读副本.此转换被初始化通过接收一个”读”消息,并且此CPU用一个”读响应”消息来响应,包含请求的数据.

转换(g): 其他CPU读取此缓存行的数据项,并且由此CPU的缓存或内存提供.在各种情况下,CPU维护一个只读副本.此被初始化通过接收一个”读”消息,并且此CPU用一个”读响应”消息响应包含请求数据.

转换(h): 此CPU实现写一些数据项在此缓存行,并且因此转送”无效”消息.CPU不能完成转换直到它接收一个完整的”无效确认”响应集合.或者,所有其他CPU弹出此缓存从它们的缓存中通过”写回”消息(推测为了腾出空间用于其他缓存行),因此此CPU是最后持有它的CPU.

转换(i): 仅当此CPU的缓存中的一个缓存行持有数据项时,一些CPU做一个原子的读-修改-写操作.因此此CPU从它的缓存中无效它的缓存行.此转换通过接收一个”读无效”消息被初始化,并且此CPU同时响应”读响应”和”无效确认”消息.

转换(j): CPU存储一个数据项到缓存行,数据项不在它的缓存中,并因此发送一个”读无效”消息.CPU不能完成转换,直到它接收到”读响应”和一个完整的”无效确认”消息集合.在存储完成后,缓存行将可能转变为”修改”状态通过转换(b)

转换(k): 此CPU装载一个不在它缓存中的数据项到缓存行,CPU发送一个”读”消息,并完成转换在接收相应的”读响应”之后.

转换(l): 其他一些CPU存储一个数据项到此缓存行,当持有此项的缓存行在只读状态,由于数据项也被持有在其他CPU缓存中(如同当前的CPU缓存).此转换被初始化通过接收一个”无效”消息,然后此CPU响应一个”无效确认”消息.

快速测验4:硬件如何处理上面描述的延时转换?

2.4 MESI协议例子

缓存一致性例子

让我们现在以数据的缓存行世界角度查看此,初始放置在内存地址0处,同时它通过4CPU系统的各种单线直接映射缓存移动.表1显示此数据流程,而第一列显示操作序列,第二列执行操作的CPU,第三列已经执行的操作.接下来的第四列,各CPU缓存行的状态(内存地址紧随MESI状态),并且最后的两列,是否对应内存内容,是数据(“V”)或不是(“I”).

初始时,将要放置数据在里面的CPU缓存行是”无效”状态,而内存中的数据是有效的.当CPU0装载地址0处的数据,在CPU0的缓存中的数据进入共享状态,而在内存中数据依然有效.CPU3也装载地址0处的数据,因此它处在”共享”状态在两个CPU的缓存中,并且仍然有效在内存中.接下来,CPU3装载一些其他缓存行(地址8),这强制地址0的数据通过一个写回出缓存,替换它用地址8的数据.CPU2现在从地址0做一个装载,但此CPU实现了立即存储它,并因此使用了一个”读无效”消息为了获取独占的副本,并使CPU3的缓存无效(虽然副本是内存中的最新值).下一个CPU2做预先存储,改变状态为”修改”.内存中的副本过期,CPU1做一个原子增加,使用”读无效”去检测CPU2缓存中的数据,并使它无效,因此CPU1缓存中的副本是在”修改”状态(并且在内存中的副本保持过期).最终,CPU1读地址8处的缓存行,它使用”写回”去放置地址0的数据回内存.

注意:结束时,数据在一些CPU的缓存中.

快速测验5: 什么样的操作序列将使CPU的缓存回”无效”状态?

0 0