Thread-Level Parallelism
简单的回顾线程级并行,线程级并行的需求很简单,就是要处理的东西太多了,单核已经不够处理,并且各种并行度已经优化到了极限,因此只能向多核拓展,让多核处理数据。线程级并行主要存在两种形态,一种是将大任务分割成小人物分配到多个核上,这也是所谓的并行处理;另一种就是各个核上自己跑自己的程序,就跟平时正常使用计算机那样。线程级并行和指令级并行具有本质上的区别,指令级并行完全是硬件上的行为,硬件的实现决定了指令集并行的效率,但是线程级并行效率可能取决于硬件实现(一致性)和程序员的程序实现(并行的处理是否分配均匀)。
指令集并行从硬件设计的角度会带来两个问题:
- 缓存一致性(conherency):其他核的写会造成两个核之间的数据不一致,需要通过硬件机制来保证数据的一致性。这就关系到缓存一致性协议。
- 内存序(consistency):其他核的写什么时候对当前核可见。其他核的写确实能被当前核可见,但是可见是在什么时候开始可见,到底是我在读取这个值之前可见还是读取这个值之后可见?体系结构要提出一套内存序的规范(到底对这个内存序做不做严格的限制,硬件上如何支持这个内存序),还要提供能够实现各种内存序的同步或者屏障指令。
缓存一致性
缓存一致性协议,要确保的是各个核之间对于某个内存地址的值是一致的,缓存一致性的协议分为两种实现方式:
- 监听式的缓存一致性协议:各个处理器监听网络或者总线上的相关请求,更新自己核上的缓存一致性状态来保持一致性。MSI,MESI都属于这种。
- 目录式的缓存一致性协议:不同于对于各个缓存块的状态分布式保存维护在核上,目录式的缓存一致性协议的维护是集中式的。往往是在 L3 共享的 cache 维护一个目录,各个核的读写都到这个目录中去查询状态。
协议太多,无法一个个记忆描述,但是只要知道缓存一致性的意义就是了。程序员编程的时候是否要考虑缓存一致性?不认为需要,这是 cpu 默认提供的机制,对程序员是透明的。cpu 不断的优化一致性协议与网络,主要都是想优化对于内存访问的带宽,如果你把大量之间都用来了一致性协议的等待上,那内存带宽就下降了。
内存序
内存序分为严格的内存序和松散的内存序。严格的内存序被称为顺序的一致性模型,只要在写后使用同步命令,这些写都会按顺序被看见,顺序一致性简化了编程,但是复杂了硬件的实现;松散的一致性协议适当或者彻底放松对于内存序的要求,想要实现顺序的内存序必须通过复杂的同步指令进行实现,等于说方便了硬件的实现,但是复杂了编程(编译器的实现)。内存序还关系到乱序执行,如果内存序实现的很强,必然对 ld/st 的指令重排做出限制,会导致乱序的实现变得复杂;相对的是较弱的内存序对于乱序执行的设计还有效率都是有好处的。
编译器在保证内存序的过程中起到了重要的作用。编程语言的内存序模型和体系结构内存序模型可能是不同的,比如说 c 语言是弱一致性模型的,但是 x86 是强一致性模型的,编译器就要实现 c 语言的一致性模型到 x86 一致性模型的转换。对于程序员而言,只要实现了编程语言上的一致性,编译器会帮助实现各个体系结构上的一致性。