Textbook:
《计算机组成与设计——硬件/软件接口》 HI
《计算机体系结构——量化研究方法》 QR
最后一节来看看如何实现parallelism
在多处理器系统中,每个处理器(core)可能有自己专属的cache,然后多处理器共享memory。在这种情况下,当多个线程(核心)对同一个变量进行读写时,就会出现不同cache中该变量的值不一样的情况(取决于cache是write back还是write through,以及相关cacheline被evict的时间等等),这样就会导致程序运行结果出错了。
因此我们需要保证reading an address should return the last value written to that address。
这里涉及到两个问题: [QR P263 ch5.2.1]
- coherence(一致性):读操作可能返回什么值。what value should be returned by a read operation(behavior to same location)
- consistency(连贯性):一个写入值什么时候可以被读操作返回。when could a write operation be finished(behavior to multiple locations)
coherent memory system
假设一个机器有两个Processor P和Q。一个coherent memory system需要保证以下三个性质:
Thread(1){ P.write(X,3) P.read(X) //这里只有单线程,Return 3 }
Thread(1){ | Thread(2){ Q.write(X,5) | P.read(X) //若P的read发生在Q的write之后,最终要保证Return 5,但并不要求when(类似分布式系统的eventually consistency) } | }
Thread(1){ | Thread(2){ Q.write(X,4) | P.read(X) Q.write(X,8) | P.read(X) } | } //要保证read出来X的顺序也是4和8
其实和分布式系统的一致性很像....coherent的定义可以理解成对于某个memory location,整个系统需要达到eventually consistency:保证对同一个地址的并行写操作在所有处理器看来都具有相同的顺序(写操作串行化:最终return结果不确定,因为取决于写操作的发生时间。但写操作的顺序必须对所有thread是一致的) [HI P318]
因为性能原因,这里肯定不能像分布式那样搞个raft或者paxos作为一致性协议hhhhh。而是要用一些硬件上的协议。
首先cache需要支持以下两种操作:(HI P318,PPT#13 P7)
- Migration:
- Replication:
Snoopy Protocol
Snoopy Protocol是最简单的一种一致性协议,意思就是一致性相关的操作要broadcast到所有的processor。这个可以通过所有processor共享的一个media(例如global bus)来实现。对于每个处理器,在Cache和global bus之间会有一个Cache Controller(CC),负责监控cache侧和bus侧的一致性相关操作(比如读写cacheline),然后及时更新操作的值。比如:[PPT#13 P9]
- if another processor wishes to write to a line, you may need to "invalidate" (I.e. discard) the copy in your own cache
- if another processor wishes to read a line for which you have a dirty copy, you may need to supply
这种思路我们在分布式系统中也接触过......它最大的问题就是scale不能太大,一般8路就是上限了,不过在多路单核处理器,没有shared Last Level Cache的情况下还是很常用的。即使在多核处理器中,也可以套用这些Cache coherence protocol,只不过global bus的另一侧从内存变成了shared Last Level Cache。下面来看看它的implementation:
1. on write through cache
HI P319
考虑一个简单的write through、no write-allocate的cache,对cacheline的任何改写操作都会立即被写回内存。假设系统中有两个CPU,每个cacheline就对应了两个block(可以理解为多个replica,每块CPU cache上各一个),我们给每个block都规定两种状态:invalid(当前数据是旧的无效数据,或者压根就cache miss)和valid(当前数据有效,是最新的值)。然后对于在某个block上进行的write操作,它会使得所有其他的block置为invalid。
也就是说,写操作必须是串行化的。如果两个处理器试图同时对同一个数据项进行写操作,它们中的一个会在竞争中获胜并完成写操作(valid)。而另一个处理器的副本被置为无效(invalid),它下次访问这个cacheline时必须先去读取最新的数据。
这个方法在一个分布式系统专家看来可能会很蠢(写操作几乎完全成了串行的),但又不是不能用......
2. on write back cache
write through cache写内存的次数较多,速度是很慢的,因此我们需要write back cache。这种情况下我们用MSI Writeback Invalidation Protocol。这次每个block有了三种状态:invalid(同上)、Shared(当前cpu的缓存中是最新的data,也不对它进行改写,和内存中以及其他replica的值一致)、Modified(当前CPU改写了缓存中的data,和内存/其他replica的值不一致了,当前缓存中的值较新)。具体又分为两种模式:
- update based protocol:当一个处理器向cache写入数据时,向其他处理器broadcast这一写操作,更新所有replica(cache)上的值。其他处理器读这个block时触发一个cache hit,直接得到最新的值。
- invalidation based protocol:当一个处理器向cache写入数据时,向其他处理器broadcast这一写操作,但只把其他replica上对应block的状态置为invalid,而并不发送具体的值。其他处理器读这个block时会触发一个cache miss,然后去内存中读更新的值。[在写操作很多的情况下可以节省traffic]
下面以invalidation为例来看看具体过程:
[PPT#14 P2, P4-P8]
3. MESI invalidation protocol
[QR P270]
在有些情况下,cache操作是不需要sharing的,比如同时运行多个不同的单线程程序,那么每个core/cache的工作都是完全独立的,这时再使用上面的MSI协议开销就有点大了。因此可以用它的改进版:MESI invalidation protocol。 [PPT #13 P15]
MESI协议在上面的基础上增加了一个Exclusive state,表示这个data只有当前CPU会用到,而且与内存中的值一致。
[PPT#14 P16]
Ref
Synchronization
QR P288
最后一节来看并行编程的问题
有两种synchronization的方式:
- Mutex Exclusion:就是OS里面学的互斥和Locks。同时只有一个设备能访问临界资源
- Event Synchronization:可以理解为进程通信。包括Global or group based(barrier)和Point-to-point(wait / signal)
在OS课上我们学过它们的概念和用法,这里来关注它们内部是怎么实现的。
Lock
[PPT#15 P3]
Barrier
[PPT#15 P12]
...