多处理器体系结构:问题与方法
根据所包含的处理器数量,可以将现有共享存储器的多处理器分为两类。 第一类称为对称(共享存储器)多处理器(SMP),或集中式共享存储器多处理器,其特点是核数量较少,通常不超过32个。由于此类多处理器中的处理器数目非常少,所以处理器可以共享一个集中式存储器并平等地访问它,这就是对称一词的由来。SMP体系结构有时也称为一致存储器访问(UMA)多处理器。
在另一种设计方法中,多处理器采用物理分布式存储器,称为分布式共享存储器(DSM)。将存储器分散在节点上,既增加了带宽,也降低了到局部存储器的延迟。DSM多处理器也称为NUMA(非一致存储器访问),因为访问时间取决于数据在存储器中的位置。
集中式共享存储器体系结构
什么是多处理器缓存一致性
缓存共享数据会引入一个新的问题。因为两个不同的处理器是通过各自的缓存来保留存储器视图的,所以针对同一存储地址,它们可能会看到不同的值。这一难题一般称为缓存一致性问题。
通俗地说,如果每次读取某一数据项都会返回该数据项的最新写入值,就说这个存储器系统是一致的。这个简单定义包含了存储器系统行为的两个方面。第一个方面称为一致性(coherence),它定义了读操作能返回什么值。第二个方面称为连贯性(consistency),它决定了一个写入值什么时候被读操作返回。
如果存储器系统满足以下条件,则说它是一致的。
- 处理器P对位置X的读操作跟在P对X的写操作之后,并且在P的写操作和读操作之间没有其他处理器对X执行写操作,此读操作总是返回P写入的值。
- 如果一个处理器对位置X的读操作紧跟在另一个处理器对X的写操作之后,读写操作的间隔时间足够长,而且在两次访问之间没有其他处理器对X执行写操作,那么该读操作将返回写入值。
- 对同一位置执行的写操作是被串行化的,也就是说,在所有处理器看来,任意两个处理器对相同位置执行的两次写操作顺序相同。
一致性的基本实现方案
为多个处理器保持缓存一致性的协议称为缓存一致性协议。目前使用的协议有两类,分别采用不同的技术来跟踪共享状态。
- 目录协议——特定物理内存块的共享状态保存在一个位置中,称为目录。共有两种目录式缓存一致性,分别是集中目录和分布式目录。
- 监听协议——如果一个缓存拥有某一物理内存块中的数据副本,它就可以跟踪该块的共享状态,而不必将共享状态保存在同一个目录中。
监听一致性协议
有两种方法可以满足上一节讨论的一致性需求。一种方法是确保处理器再写入某一数据项前,获取对该数据项的独占访问。这种类型的协议称为写无效协议(write invalid protocol),因为它在执行写操作时会使其他副本无效。这是目前最常用的协议。
无效协议的一种替代方法是在写入一个数据项时更新该数据项的所有缓存副本。这种类型的协议成为写入更新(write update)或写入广播(write broadcast)协议。
分布式共享存储器和目录式一致性
监听一致性协议的替代方法是目录协议。目录中保存了每个可缓存块的状态。在多核中使用单个目录的解决方案是不可扩展的,这个目录必须是分布式的,并且其分布方式必须能够让一致性协议知道去哪里寻找存储器所有缓存块的目录信息。显而易见的解决方案是将这个目录与存储器一起分配,使不同的一致性请求可以访问不同的目录,就像不同的存储器请求访问不同的存储器一样。
目录是缓存一致性协议:基础知识
在简单协议中,状态可能为下列各项之一。
- 共享——一个或多个节点缓存了这个块,存储器的值是最新的(所有缓存中也是如此)
- 未缓存——所有节点都没有这个缓存块的副本
- 已修改——只有一个节点有这个缓存块的副本,它已经对这个块进行了写操作,所以存储器副本已经过期。这个处理器称为这个块的拥有者。
示例目录协议
当块处于未缓存状态时,存储器中的副本就是当前值,所以对这个块的请求只能是以下两种
- 读缺失
- 写缺失
当块处于共享状态时,存储器值是最新的,所以可能出现相同的两个请求。
- 读缺失
- 写缺失
当块处于独占状态时,块的当前值保存在一个结点的缓存中,而这个节点有共享者(拥有者)集合识别,所以共有三种可能的目标请求。
- 读缺失
- 数据写回
- 写缺失
同步:基础知识
基本硬件原语
构建同步操作的一个典型操作就是原子交换(atomic exchange),它会将寄存器中的一个值与存储器中的一个值交换。
实现单个原子存储器操作会引入一些挑战,因为它需要在单个不可中断的指令中进行存储器读写操作。这一要求增加了一致性实现的复杂性,因为硬件不允许在读取与写入之间插入任何其他操作,而且不能死锁。
替代方法是利用一对指令,其中第二条指令返回一个值,从这个值可以判断出这对指令是否向源自指令一样执行。如果任意处理器执行的所有其他指令要么在这对指令之前执行,要么在这对指令之后执行,就可以认为这对指令具有原子性。
在RISC-V中,这种指令对包含一个名为保留载入(load reserved)的特殊载入指令[也称为链接载入(load linked)或锁定载入(load locked)]和一个名为条件存储(store conditional)的特殊存储指令。保留载入将rs1指示的存储器内容加载到rd中,并在该存储器地址上创建一个保留。条件存储将rs2中的值存储到rs1提供的存储器地址中。如果对同一存储地址的写操作破坏了对该载入的保留,则条件存储失败并将非零写入rd;如果成功,条件存储写入0。如果处理器在两条指令之间进行了上下文切换,那么条件存储总是失败。
使用一致性实现锁
在拥有原子操作之后,就可以使用多处理器的一致性机制来实现自旋锁(spin lock)——处理器不断尝试获取的锁,它在循环中自旋,直到成功为止。在两种情况下会用到自旋锁:程序员希望短时间拥有这个锁;程序员希望当这个锁可用时,锁定过程的延迟较低。
最简单的实现方法是在存储器中保存锁变量,在没有缓存一致性时会使用这种实现方式。
如果多处理器支持缓存一致性,就可以使用一致性机制将锁放在缓存中,以保持锁值的一致性。
同步性能问题
简单自旋锁不能很好地适应可缩放性,下面将讨论对自旋锁实现的改进。
并行循环程序中另一个常用的同步操作是栅栏(Barrier)同步。栅栏强制所有到达该栅栏的进程进行等待,知道全部的进程到达栅栏,然后释放全部的进程,从而形成同步。栅栏的典型实现是要用两个自旋锁:一个用来记录到达栅栏的进程数,另一个用来封锁进程直至最后一个进程到达栅栏。栅栏的实现中要不停地探测指定的变量,直到它满足规定的条件。