1
1
mirror of https://github.com/foxsen/archbase.git synced 2026-02-02 18:09:17 +08:00
Files
archbase/21-multicore.Rmd
Zhang Fuxin ae64014380 gs464v => la464
GS464v => LA464
2021-11-02 21:45:06 +08:00

423 lines
72 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多核处理结构
多核处理器Multicore Processor在单芯片上集成多个处理器核也称为单片多处理器Chip Multi-Processor简称CMP广泛应用于个人移动设备Personal Mobile Device简称PMD、个人电脑PC、服务器、高性能计算机等领域。本章从结构角度对多核处理器进行分析。
## 多核处理器的发展演化
多核处理器在单芯片上集成多个处理器核通过聚合芯片上的多个处理器核的计算能力来提高应用程序执行性能。多核处理器大致可从以下方面进行分类从核的数量角度可分为多核处理器和众核处理器一般大于64核为众核处理器从处理器核的结构角度可分为同构和异构同构是指核结构是相同的而异构是指核结构是不同的从适用应用角度可分为面向桌面电脑、服务器等应用的通用多核处理器以及面向特定应用的多核/众核处理器如GPU可看作是一种特定的众核处理器具有很高的浮点峰值性能。
多核处理器主要在多处理器系统的研究基础上发展而来。多处理器系统的研究已经有几十年的历史。20世纪七、八十年代由于单个处理器的性能满足不了应用的需求开始出现多处理器系统。20世纪八、九十年代很多高档工作站都有2\~4个处理器用于科学计算的高性能计算机处理器个数更多。国际上对计算机性能有一个TOP500排名每6个月列出当时世界上最快的前500台计算机这些计算机都有成千上万个处理器。从20世纪90年代后期开始随着半导体工艺的发展单芯片上晶体管数目大幅增多多核处理器得到了很好的发展。学术界最早的多核处理器项目Hydra是由美国斯坦福大学于1994年研究的。在工业界IBM公司于2001年推出IBM Power4双核处理器AMD于2005年推出第一款X86架构双核处理器Intel于2006年推出第一款酷睿双核处理器国内于2009年推出了第一款四核龙芯3A处理器。
很明显可以看出,多核处理器是在多处理器系统上发展的,其发展的主要驱动力包括以下三个方面。
1半导体工艺发展
摩尔定律是过去40多年间描述半导体工艺发展的经验法则。1965年Gordon MooreIntel 公司联合创始人提出半导体芯片上集成的晶体管和电阻数量将每年增加一倍。1975年对摩尔定律进行了修正把“每年增加一倍”改为“每两年增加一倍”。现在摩尔定律流行的表述为集成电路芯片上所集成的晶体管数目每隔18个月就翻一倍。目前主流处理器工艺已经到14nm-7nm工艺在单芯片上集成数十亿甚至上百亿个晶体管。不过摩尔定律不可能永远延续2015年ITRSInternational Technology Roadmap for Semiconductors预测晶体管尺寸可能在2021年后停止缩小。目前工艺升级的速度已经从1-2年升级一代放慢到3-5年升级一代而且工艺升级带来的性能、成本、功耗的好处已经不大。
2功耗墙问题
功耗墙问题也是处理器从单核转到多核设计的一个非常重要的因素。面对单芯片上的大量晶体管,如何设计处理器有两种思路,一种是单芯片设计复杂的单处理器核,另一种是单芯片设计多个处理器核。理论来说采用后一种思路性能功耗比收益较大。芯片功耗主要由静态功耗和动态功耗组成,而动态功耗则由开关功耗和短路功耗组成。其中开关功耗是由芯片中电路信号翻转造成的,是芯片功耗的主体。下面给出了开关功耗的计算公式,其中$C_{load}$为电路的负载电容,$V$为电路的工作电压,$f$为电路的时钟频率。
$$ P_{switch} = \frac{1}{2} C_{load} V^{2} f $$
单芯片设计复杂单处理器核提高性能的主要方法包括通过微结构优化提高每个时钟周期发射执行的指令数以及通过提高主频来提高性能。微结构优化的方法由于受到程序固有指令级并行性以及微结构复杂性等因素限制在达到每个时钟周期发射执行4条指令后就很难有性能收益。提高电压和主频的方法导致功耗随着主频的提高超线性增长。例如通过电压提升10%可以使主频提升10%根据开关功耗计算公式开关功耗跟主频成正比跟电压的平方成正比即在一定范围内功耗与主频的三次方成正比主频提高10%导致功耗提高30%。
单芯片设计多个处理器核提高性能的方法通过增加处理器核的个数提升处理器并行处理的性能。当处理器核数目增加N倍时功耗也大致增加N倍性能也增加N倍此处性能主要指运行多个程序的吞吐率也就是说功耗随着性能的提高线性增长。
2005年以前单芯片设计复杂单处理器核以提高性能是微处理器发展的主流。以Intel公司由于功耗墙问题放弃4GHz的Pentium IV处理器研发为标志2005年之后单芯片设计多处理器核成为主流。
3并行结构的发展
多处理器系统经过长期发展,为研制多核处理器打下了很好的技术基础。例如,多处理器系统的并行处理结构、编程模型等可以直接应用于多核处理器上。因此有一种观点认为:将传统多处理器结构实现在单芯片上就是多核处理器。
在处理器内部、多个处理器之间以及多个计算机节点之间有多种不同的并行处理结构。
**1SIMD结构。** 指采用单指令同时处理一组数据的并行处理结构。采用SIMD结构的Cray系列向量机包含向量寄存器和向量功能部件单条向量指令可以处理一组数据。例如Cray-1的向量寄存器存储64个64位的数据CrayC-90的向量寄存器存储128个64位的数据。以Cray系列向量机为代表的向量机在20世纪70年代和80年代前期曾经是高性能计算机发展的主流在商业、金融、科学计算等领域发挥了重要作用其缺点是难以达到很高的并行度。如今虽然向量机不再是计算机发展的主流但目前的高性能处理器普遍通过SIMD结构的短向量部件来提高性能。例如Intel处理器的SIMD指令扩展实现不同宽度数据的处理如SSEStreaming SIMD Extensions扩展一条指令可实现128位数据计算可分为4个32位数据或者2个64位数据或者16个8位数据AVXAdvanced Vector Extensions扩展可实现256位或者512位数据计算。
**2对称多处理器Symmetric MultiProcessor简称SMP结构。** 指若干处理器通过共享总线或交叉开关等统一访问共享存储器的结构各个处理器具有相同的访问存储器性能。20世纪八九十年代DEC、SUN、SGI等公司的高档工作站多采用SMP结构。这种系统的可伸缩性也是有限的。SMP系统常被作为一个节点来构成更大的并行系统。多核处理器也常采用SMP结构往往支持数个到十多个处理器核。
**3高速缓存一致非均匀存储器访问Cache Coherent Non-Uniform Memory Access简称CC-NUMA结构。**CC-NUMA结构是一种分布式共享存储体系结构其共享存储器按模块分散在各处理器附近处理器访问本地存储器和远程存储器的延迟不同共享数据可进入处理器私有高速缓存并由系统保证同一数据的多个副本的一致性。CC-NUMA的可扩展性比SMP结构要好支持更多核共享存储但由于其硬件维护数据一致性导致复杂性高可扩展性也是有限的。典型的例子有斯坦福大学的DASH和FLASH以及20世纪90年代风靡全球的SGI的Origin 2000。IBM、HP的高端服务也采用CC-NUMA结构。Origin 2000可支持上千个处理器组成CC-NUMA系统。有些多核处理器也支持CC-NUMA扩展例如4片16核龙芯3C5000处理器通过系统总线互连直接形成64核的CC-NUMA系统。
**4MPPMassive Parallel Processing系统。**指在同一地点由大量处理单元构成的并行计算机系统。每个处理单元可以是单机也可以是SMP系统。处理单元之间通常由可伸缩的互连网络如Mesh、交叉开关网络等相连。MPP系统主要用于高性能计算。
**5机群系统。**指将大量服务器或工作站通过高速网络互连来构成廉价的高性能计算机系统。机群计算可以充分利用现有的计算、内存、文件等资源用较少的投资实现高性能计算也适用于云计算。随着互连网络的快速发展机群系统和MPP系统的界限越来越模糊。
从结构的角度看多处理器系统可分为共享存储系统和消息传递系统两类。SMP和CC-NUMA结构是典型的共享存储系统。在共享存储系统中所有处理器共享主存储器每个处理器都可以把信息存入主存储器或从中取出信息处理器之间的通信通过访问共享存储器来实现。MPP和机群系统往往是消息传递系统在消息传递系统中每个处理器都有一个只有它自己才能访问的局部存储器处理器之间的通信必须通过显式的消息传递来进行。
尽管消息传递的多处理器系统对发展多核处理器也很有帮助如GPU但是通用多核处理器主要是从共享存储的多处理器系统演化而来。多核处理器与早期SMP多路服务器系统在结构上并没有本质的区别。例如多路服务器共享内存通过总线或者交叉开关实现处理器间通信多核处理器共享最后一级Cache和内存通过片上总线、交叉开关或者Mesh网络等实现处理器核间通信。
通用多核处理器用于手持终端、桌面电脑和服务器,是最常见、最典型的多核处理器,通常采用共享存储结构,它的每个处理器核都能够读取和执行指令,可以很好地加速多线程程序的执行。本章主要以通用多核处理器为例来分析多核处理器结构。通用多核处理器结构设计与共享存储多处理器设计的主要内容相似,包括多核处理器的访存结构、多核处理器的互连结构、多核处理器的同步机制等。
## 多核处理器的访存结构
通用多核处理器采用共享存储结构,其设计存在如下关键问题:
1片上Cache如何组织与单核处理器类似多核处理器需要在片上设置大容量的Cache来缓解芯片计算能力与访存性能之间日益扩大的差距。片上Cache如何组织Cache结构采用私有还是共享集中式还是分布式这些是需要设计者考虑的问题。
2多个处理器核发出的访存指令次序如何约定各处理器核并行执行线程或者进程发出读/写load/store访存指令这些访问指令的执行次序如何约定使得应用程序员可以利用这些约定来推理程序的执行结果。存储一致性模型就是用来解决这方面问题的。
3如何维护Cache数据一致性一个数据可能同时在多个处理器核的私有Cache中和内存中存在备份如何保证数据一致性Cache一致性协议将解决Cache一致性问题。
### 通用多核处理器的片上Cache结构
片上Cache结构是通用多核处理器设计的重要内容。片上Cache的种类主要有私有Cache、片上共享Cache、片间共享Cache。图\@ref(fig:cache-structure)a是私有Cache结构示意图图\@ref(fig:cache-structure)b是片上共享Cache结构示意图由于一级Cache的访问速度对性能影响大通用多核处理器的一级Cache几乎都是私有的。私有Cache结构具有较快的访问速度但是具有较高的失效率。共享Cache结构的访问速度稍慢但具有失效率低的优点。多处理器芯片间共享Cache结构的访问速度慢且失效率高因此并不常用。
```{r cache-structure, fig.cap='Cache结构示意图', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/cache_structure.png')
```
<!-- 图11.1 Cache结构图 -->
目前主流多核处理器的典型Cache 结构是片内共享最后一级CacheLast Level Cache简称LLC片间共享内存。表\@ref(tab:cache-parameter)列出了典型商用多核处理器的Cache结构参数。处理器核的一级Cache和二级Cache私有三级CacheLLC共享。有些处理器甚至有片外的四级Cache例如Intel i7处理器。
```{r cache-parameter, echo = FALSE, message=FALSE, tab.cap='商用多核处理器主要参数示例'}
autonum <- run_autonum(seq_id = "tab", bkm = "cache-parameter", bkm_all = TRUE)
readr::read_csv('./materials/chapter11/cache_parameter.csv') %>%
flextable() %>%
set_caption(caption="商用多核处理器主要参数示例", autonum = autonum) %>%
theme_box() %>%
autofit()
```
在共享LLC结构中主要有UCAUniform Cache Access和NUCANon-Uniform Cache Access两种。图\@ref(fig:shared-llc)为共享LLC结构示意图假设二级Cache为LLC
```{r shared-llc, fig.cap='共享LLC结构示意图', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/shared_llc.png')
```
UCA是一种集中式共享结构多个处理器核通过总线或者交叉开关连接LLC所有处理器核对LLC的访问延迟相同。这种集中式的共享LLC很容易随着处理器核数目的增加成为瓶颈。另外UCA结构由于使用总线或者交叉开关互连可扩展性受限。因此通常在处理器核数较少的多核处理器中采用UCA结构例如四核龙芯3号处理器。
NUCA是一种分布式共享结构每个处理器核拥有本地的LLC并通过片上互连访问其他处理器核的LLC。在NUCA结构中处理器核可以访问所有的LLC但是不同位置的LLC具有不同的访问延迟。当工作集较小时处理器核的本地Cache足够容纳工作集处理器核只使用本地Cache当工作集较大时本地Cache中放不下的数据可以放到远地Cache中。NUCA结构需要高效Cache查找和替换算法使得在使用远地Cache时不影响性能。NUCA结构中通常采用可扩展的片上互连如Mesh片上网络等采用基于目录的Cache一致性协议具有良好的可扩展性可以有效支持较多数目的处理器核。因此在具有较多核数的多核/众核处理器中通常采用NUCA结构如SPARC M7和龙芯3C5000等。
### 存储一致性模型
本节简要介绍常见的存储一致性模型。存储一致性模型最初是针对共享存储的多处理器设计提出来的,同样也可以适用于多核处理器设计。本节在介绍存储一致性模型时,处理器(处理机)和处理器核在概念是可以互用的。
下面举一个存储一致性问题的例子。如表\@ref(tab:shared-memory-program)所示寄存器R1为进程P2的内部寄存器R2和R3为P3的内部寄存器初始值均为0变量a,b为P1、P2和P3的共享变量初始值均为0。
```{r shared-memory-program, tab.cap='共享存储程序片段', echo = FALSE}
autonum <- run_autonum(seq_id = "tab", bkm = "shared-memory-program", bkm_all = TRUE)
dt <- data.frame('P1' = 'L11: STORE a, 1;',
'P2' = 'L21: LOAD R1, a;\nL22: STORE b, 1;',
'P3' = 'L31: LOAD R2, b;\nL32: LOAD R3, a;')
flextable(dt) %>%
set_caption(caption="共享存储程序片段", autonum = autonum) %>%
width(width=2.0) %>%
theme_box()
```
在表\@ref(tab:shared-memory-program)所示的程序中如果仅要求P1、P2及P3根据指令在程序中出现的次序来执行指令那么这个程序的访存事件可能按如下次序发生
1. P1发出存数操作L11
2. L11到达P2但由于网络堵塞等原因L11未到达P3
3. P2发出取数操作L21 取回a的新值
4. P2发出存数操作L22且其所存的b新值到达P3
5. P3发出取数操作L31取回b的新值
6. P3发出取数操作L32但由于L11 未到达P3故L32取回a的旧值
7. L11到达P3。
这是一个程序员难以接受的执行结果。因为从程序员的观点来看如果L21 和L31分别取回a 和b 的新值则说明存数操作L11 和L22 都已完成L32必然取回a 的新值。在此例中,即使每个处理器都根据指令在程序中出现的次序来执行指令,仍然会导致错误的结果。从这个例子可以看出,在共享存储系统中,需要对多处理器的访存操作的次序做出限制,才能保证程序执行的正确。
存储一致性模型是多处理器系统设计者与应用程序员之间的一种约定,它给出了正确编写程序的标准,使得程序员无须考虑具体访存次序就能编写正确程序,而系统设计者则可以根据这个约定来优化设计提高性能。系统设计者通过对各处理器的访存操作完成次序加以必要的约束来满足存储一致性模型的要求。
文献中常见的存储一致性模型包括:顺序一致性模型、处理器一致性模型、弱一致性模型、释放一致性模型等。这些存储一致性模型对访存事件次序的限制不同,因而对程序员的要求以及所能得到的性能也不一样。存储一致性模型对访存事件次序施加的限制越弱越有利于提高性能,但编程越难。下面介绍具体的存储一致性模型。
**1顺序一致性Sequential Consistency简称SC模型。**这种模型是程序员最乐于接受的存储一致性模型,最符合程序员的直觉。对于满足顺序一致性的多处理机中的任一执行,总可以找到同一程序在单机多进程环境下的一个执行与之对应,使得二者结果相等。
为了放松对访存事件次序的限制,人们提出了一系列弱存储一致性模型。 这些弱存储一致性模型的基本思想是:在顺序一致性模型中,虽然为了保证正确执行而对访存事件次序施加了严格的限制,但在大多数不会引起访存冲突的情况下,这些限制是多余的,极大地限制了系统优化空间进而影响了系统性能。因此可以让程序员承担部分执行正确性的责任,即在程序中指出需要维护一致性的访存操作,系统只保证在用户指出的需要保持一致性的地方维护数据一致性,而对用户未加说明的部分,可以不考虑处理器之间的数据相关。
**2处理器一致性Processor Consistency简称PC模型。**这种模型比顺序一致性模型弱故对于某些在顺序一致条件下能正确执行的程序在处理器一致条件下执行时可能会导致错误结果。处理器一致性模型对访存事件发生次序施加的限制是在任一取数操作load被允许执行之前所有在同一处理器中先于这一load的取数操作都已完成在任一存数操作store被允许执行之前所有在同一处理器中先于这一store的访存操作包括load和store都已完成。上述条件允许store之后的load越过store而执行在实现上很有意义在Cache命中的load指令写回之后但没有提交之前如果收到其他处理器对load所访问Cache行的无效请求load指令可以不用取消较大地简化了流水线的设计。多核龙芯3号处理器设计中就采用了处理器一致性。
**3弱一致性Weak Consistency简称WC模型。**这种模型的主要思想是把同步操作和普通访存操作区分开来,程序员必须用硬件可识别的同步操作把对可写共享单元的访问保护起来,以保证多个处理器对可写共享单元的访问是互斥的。弱一致性模型对访存事件发生次序做如下限制:同步操作的执行满足顺序一致性条件;在任一普通访存操作被允许执行之前,所有在同一处理器中先于这一访存操作的同步操作都已完成;在任一同步操作被允许执行之前,所有在同一处理器中先于这一同步操作的普通访存操作都已完成。上述条件允许在同步操作之间的普通访存操作执行时不用考虑进程之间的相关。虽然弱一致性模型增加了程序员的负担,但它能有效地提高性能。值得指出的是,即使是在顺序一致的共享存储并行程序中,同步操作也是难以避免的,否则程序的行为难以确定。因此,在弱一致性模型的程序中,专门为数据一致性而增加的同步操作不多。
**4释放一致性Release Consistency简称RC模型。**这种模型是对弱一致性模型的改进它把同步操作进一步分成获取操作acquire和释放操作release。acquire 用于获取对某些共享存储单元的独占性访问权而release 则用于释放这种访问权。释放一致性模型对访存事件发生次序做如下限制同步操作的执行满足顺序一致性条件在任一普通访存操作被允许执行之前所有在同一处理器中先于这一访存操作的acquire操作都已完成在任一release操作被允许执行之前所有在同一处理器中先于这一release的普通访存操作都已完成。
### Cache一致性协议
在共享存储的多核处理器中存在Cache一致性问题即如何使同一数据块在不同Cache以及主存中的多个备份保持数据一致的问题。具体来说一个数据块可能在主存和Cache之中保存多份而不同的处理器核有可能同时读取或者修改这个数据导致不同的处理器核观察到的数据的值是不同的。Cache一致性协议Cache Coherence Protocol是指在共享存储的多处理器或者多核处理器系统中一种用来保持多个Cache之间以及Cache与主存之间数据一致的机制。人们已经提出了若干Cache一致性协议来解决这个问题。
1.Cache一致性协议的分类
Cache一致性协议的具体作用就是把某个处理器核新写的值传播给其他处理器核以确保所有处理器核看到一致的共享存储内容。从如何传播新值的角度看Cache一致性协议可分为写无效Write-Invalidate也可称为写使无效协议与写更新Write-Update协议从新值将会传播给谁的角度看它可以分为侦听协议与目录协议。Cache一致性协议决定系统为维护一致性所做的具体动作因而直接影响系统性能。
**1写无效协议与写更新协议。**在写无效协议中,当根据一致性要求要把一个处理器核对某一单元所写的值传播给其他处理器核时,就使其他处理器核中该单元的备份无效;其他处理器核随后要用到该单元时,再获得该单元的新值。在写更新协议中,当根据一致性要求要把一个处理器核对某一单元所写的值传播给其他处理器核时,就把该单元的新值传播给所有拥有该单元备份的处理器核,对相应的备份进行更新。
写无效协议的优点是一旦某处理器核使某一变量在所有其他Cache中的备份无效后它就取得了对此变量的独占权随后它可以随意地更新此变量而不必告知其他处理器核直到其他处理器核请求访问此变量而导致独占权被剥夺。其缺点是当某变量在一处理器核中的备份变无效后此处理器核再读此变量时会引起Cache不命中在一个共享块被多个处理器核频繁访问的情况下会引起所谓的“乒乓”效应即处理器核之间频繁地互相剥夺对一个共享块的访问权而导致性能严重下降。写更新协议的优点是一旦某Cache缓存了某一变量它就一直持有此变量的最新备份除非此变量被替换掉。其缺点是写数的处理器核每次都得把所写的值传播给其他处理器核即使其他处理器核不再使用所写的共享块。写无效协议适用于顺序共享Sequential Sharing的程序即在较长时间内只有一个处理器核访问一个变量而写更新协议适用于紧密共享Tight Sharing的程序即多个处理器核在一段时间内频繁地访问同一变量。
**2侦听协议与目录协议。**侦听协议的基本思想是当一个处理器核对共享变量的访问不在Cache 命中或可能引起数据不一致时它就把这一事件广播到所有处理器核。系统中所有处理器核的Cache都侦听广播当拥有广播中涉及的共享变量的Cache侦听到广播后就采取相应的维持一致性的行动使本Cache的备份无效、向总线提供数据等。侦听协议实现较简单每个处理器核Cache只需要维护状态信息就可以了。侦听协议适合于通过总线互连的多核处理器因为总线是一种方便而快捷的广播媒介。在写使无效侦听协议中当一个Cache侦听到其他处理器核欲写某一单元且自己持有此单元的备份时就使这一备份无效以保持数据一致性在写更新侦听协议中当一个Cache侦听到自己持有备份的某一共享单元的内容被其他处理器核所更新时就根据侦听到的内容更新此备份的值。
由于侦听协议需要广播因此只适用于共享总线结构。总线是一种独占式资源且总线延迟随所连接的处理器核数目的增加而增加存在可伸缩性差的问题。在采用片上网络互连的多核处理器中通常使用基于目录的Cache一致性协议。目录协议的主要思想是为每一存储行维持一目录项该目录项记录所有当前持有此行备份的处理器核号以及此行是否已被改写等信息。当一个处理器核欲往某一存储行写数且可能引起数据不一致时它就根据目录的内容只向持有此行的备份的那些处理器核发出写使无效/写更新信号从而避免了广播。典型的目录组织方式为位向量目录。位向量目录中的每一目录项有一个n位的向量其中n是系统中处理器核的个数。位向量中第i位为“1”表示此存储行在第i个处理器核中有备份。每一目录项还有一改写位当改写位为“1”时表示某处理器核独占并已改写此行。位向量目录的缺点是所需的目录存储器容量随处理器核数n以及共享存储容量m的增加以O(m*n)的速度增加,有较大存储开销。
2.Cache 状态
Cache一致性协议的实现方式为在Cache中每一个Cache行设置一致性状态来记录该Cache行的读写状态确保Cache行不会被多个处理器核同时修改。Cache行的一致性状态的实现有多种具体形式如最简单的三状态ESI较为常见的MESI及其变种MOESI等。
ESI 是指Cache 行的三种一致性状态EExclusive独占SShared共享I Invalid无效。Invalid状态表示当前Cache行是无效的对其进行任何读写操作都会引发缓存缺失Cache Miss。Shared状态表明当前Cache行可能被多个处理器核共享只能读取不能写入对其写入也会引发缓存缺失。Exclusive状态表明对应Cache行被当前处理器核独占该处理器核可以任意读写这个Cache行而其他处理器核如果想读写这个Cache行需要请求占有这个Cache行的处理器核释放该Cache行。图\@ref(fig:esi-transit)给出了三个状态之间的转换关系。
```{r esi-transit, fig.cap='三状态Cache一致性协议状态转换图', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/esi_transit.png')
```
MESI 在ESI 的基础上增加了MModified修改状态。其中Shared状态和Invalid状态和ESI的完全一样而Exclusive 状态表示当前Cache块虽然被当前处理器核独占但是还没有被修改与内存中的数据保持一致如果处理器核想将其替换出去并不需要将该Cache行写回内存。Modified状态表示当前Cache行被当前处理器核独占并且已经被修改过了如果处理器核想替换该Cache行需要将该Cache行写回内存。与ESI协议相比增加一个Modified状态的优点是减少了Cache到内存的数据传输次数Cache只需要将Modified状态的Cache行写回内存。
下面通过一个写无效的位向量目录协议例子简单说明Cache一致性协议的工作原理。通常,一个Cache一致性协议应包括以下三方面的内容Cache行状态、存储行状态以及为保持Cache一致性的状态转化规则。
该协议采用ESI实现Cache的每一行都有三种状态无效状态INV、共享状态SHD以及独占状态EXC。在存储器中每一行都有一相应的目录项。每一目录项有一n位的向量其中n是系统中处理器核的个数。位向量中第i位为“1”表示此存储行在第i个处理器核Pi中有备份。此外每一目录项有一改写位当改写位为“1”时表示某处理器核独占并已改写此行相应的存储行处于DIRTY 状态否则相应的存储行处于CLEAN 状态。
当处理器核Pi发出一取数操作“LOAD x”时根据x在Cache 和存储器中的不同状态采取如下不同的操作若x在Pi的Cache中处于共享或独占状态则取数操作“LOAD x”在Cache 命中。若x在Pi的Cache 中处于无效状态那么这个处理器核向存储器发出一个读数请求read(x)。存储器在收到这个read(x)后查找与单元x相对应的目录项如果目录项的内容显示出x所在的存储行处于CLEAN 状态改写位为“0”即x在存储器的内容是有效的那么存储器向发出请求的处理器核Pi发出读数应答rdack(x)提供x所在行的一个有效备份并把目录项中位向量的第i位置为“1”如果目录项的内容显示出x所在的存储行已被某个处理器核Pk改写改写位为“1”那么存储器向Pk发出一个写回请求wtbk(x)Pk在收到wtbk(x)后把x在Cache的备份从独占状态EXC改为共享状态SHD并向存储器发出写回应答wback(x)提供x所在行的一个有效备份存储器收到来自Pk的wback(x)后向发出请求的处理器核Pi发出读数应答rdack(x)提供x所在行的一个有效备份把目录项中的改写位置为“0”并把位向量的第i位置为“1”。如果x不在Pi的Cache中那么Pi先从Cache中替换掉一行再向存储器发出一个读数请求read(x)。
当处理器核Pi发出一存数操作“STORE x”时根据x 在Cache和存储器中的不同状态采取如下不同的操作若x在Pi的Cache中处于独占状态则存数操作“STORE x”在Cache 命中。若x在Pi的Cache中处于共享状态那么这个处理器核向存储器发出一个写数请求write(x)存储器在收到这个write(x)后查找与单元x 相对应的目录项如果目录项的内容显示出x所在的存储行处于CLEAN 状态改写位为“0”并没有被其他处理器核所共享位向量中所有位都为“0”那么存储器向发出请求的处理器核Pi发出写数应答wtack(x)表示允许Pi独占x所在行把目录项中的改写位置为“1”并把位向量的第i位置为“1”如果目录项的内容显示出x所在的存储行处于CLEAN 状态改写位为“0”并且在其他处理器核中有共享备份位向量中有些位为“1”那么存储器根据位向量的内容向所有持有x的共享备份的处理器核发出一个使无效信号invld(x)持有x的有效备份的处理器核在收到invld(x)后把x在Cache的备份从共享状态SHD改为无效状态INV并向存储器发出使无效应答invack(x)存储器收到所有invack(x)后向发出请求的处理器核Pi发出写数应答wtack(x)把目录项中的改写位置为“1”并把位向量的第i位置为“1”其他位清“0”。若x在Pi的Cache中处于无效状态那么这个处理器核向存储器发出一个写数请求write(x)存储器在收到这个write(x)后查找与单元x相对应的目录项如果目录项的内容显示出x所在的存储行处于CLEAN 状态改写位为“0”并没有被其他处理器核所共享位向量中所有位都为“0”那么存储器向发出请求的处理器核Pi发出写数应答wtack(x)提供x所在行的一个有效备份把目录项中的改写位置为“1”并把位向量的第i位置为“1”如果目录项的内容显示出x所在的存储行处于CLEAN 状态改写位为“0”并且在其他处理器核中有共享备份位向量中有些位为“1”那么存储器根据位向量的内容向所有持有x的共享备份的处理器核发出一个使无效信号invld(x)持有x的有效备份的处理器核在收到invld(x)后把x在Cache 的备份从共享状态SHD改为无效状态INV并向存储器发出使无效应答invack(x)存储器收到所有invack(x)后向发出请求的处理器核Pi发出写数应答wtack(x)提供x所在行的一个有效备份把目录项中的改写位置为“1”并把位向量的第i 位置为“1”其他位清“0”如果目录项的内容显示出x所在的存储行已被某个处理器核Pk改写改写位为“1”位向量第k 位为“1”那么存储器向Pk发出一个使无效并写回请求invwb(x)Pk在收到invwb(x)后把x在Cache的备份从独占状态EXC改为无效状态INV并向存储器发出使无效并写回应答invwback(x)提供x所在行的有效备份存储器收到来自Pk 的invwback(x)后向发出请求的处理器核Pi发出写数应答wtack(x)提供x所在行的一个有效备份把目录项中的改写位置为“1”并把位向量的第i位置为“1”其他位清“0”。如果x不在Pi的Cache中那么Pi先从Cache中替换掉一行再向存储器发出一个写数请求write(x)。
如果某处理器核要替换一Cache行且被替换行处在EXC状态那么这个处理器核需要向存储器发出一个替换请求rep(x)把被替换掉的行写回存储器。
假设单元x 初始时在存储器中处于CLEAN状态(改写位为“0”)并被处理器核Pj和Pk所共享在Pj和Pk的Cache中处于SHD状态如图\@ref(fig:dir-invld)a所示。接着x被多个处理器核按如下次序访问处理器核Pi发出存数操作“STORE x”处理器核Pk发出存数操作“STORE x”处理器核Pi发出取数操作“LOAD x”处理器 Pj发出取数操作“LOAD x”。图\@ref(fig:dir-invld)b\~f显示出上述访问序列引起的一系列消息传递以及x在Cache及在存储器中的状态的转化过程。
```{r dir-invld, fig.cap='基于目录的写无效Cache一致性协议', fig.align='center', fig.show="hold", echo = FALSE, out.width='80%'}
knitr::include_graphics('./images/chapter11/dir_invld.png')
```
## 多核处理器的互连结构
多核处理器通过片上互连将处理器核、Cache、内存控制器、IO 接口等模块连接起来。图\@ref(fig:nuca-interconnect)为一个NUCA结构的多核处理器的片上互连示意图。常见的片上互连结构包括片上总线、交叉开关和片上网络。图\@ref(fig:interconnect-type)为三种结构的对比示意图。其中共享总线结构和交叉开关结构因可伸缩性差的原因主要用于小规模的多核处理器片上网络Network-on-Chip简称NOC具有可伸缩性好的优势适合于核数较多的多核/众核处理器。
```{r nuca-interconnect, fig.cap='NUCA架构多核处理器的片上互连', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/nuca_interconnect.png')
```
```{r interconnect-type, fig.cap='片上互连结构分类', fig.align='center', fig.show="hold", echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/interconnect.png')
```
**1.片上总线**
传统的计算机系统的总线通常由一组信号线把多功能模块连接在一起。通过信号线上的信号表示信息通过约定不同信号的先后次序约定操作如何实现。根据传输信息的种类不同可以划分为数据总线、地址总线和控制总线分别用来传输数据、数据地址和控制信号。标准化的总线可以方便各部件间互连因此出现了许多总线标准例如ISA、PCI、USB总线标准等。
片上总线主要用于多核处理器设计它是片上各个部件间通信的公共通路由一组线组成。片上总线标准通常包括总线位宽、总线时序、总线仲裁等。常见的片上总线标准包括IBM公司的CoreConnect、ARM公司的AMBA总线标准、Silicore公司的Wishbone片上总线协议等。
片上总线的优点是实现简单,在其上易于实现广播通信,其缺点主要是可伸缩性不好。片上总线是一种独占式资源,其总线延迟随所连接节点数的增加而增加,每个节点分得的总线带宽随连接节点数的增加而较少,导致可伸缩性不好。片上总线适合用在连接节点不多的场合,常用于处理器核不多的多核处理器中。
**2.交叉开关**
交叉开关可以看作一个以矩阵形式组织的开关集合。在一个M个输入、N个输出的交叉开关中每个输出端口都可以接任意输入端口。交叉开关有多个输入线和输出线这些线交叉连接在一起交叉点可以看作单个开关。当一个输入线与输出线的连接点处开关导通时则在输入线与输出线之间建立一个连接。交叉开关具有非阻塞Non-blocking特性可以建立多个输入与输出之间的连接在不存在冲突的情况下这些连接上的通信不会互相干扰。采用交叉开关通信的两个节点独享该连接的带宽当有多对节点之间建立连接进行通信时总带宽就会变大。
交叉开关的优点是高带宽多对输入与输出端口间可以并行通信且总带宽随所连接节点数的增加而增加。但缺点是随着连接节点数的增加交叉开关需要的交叉点数目增加较快物理实现代价较高复杂度为O(M*N)因此可伸缩性有限也不适合连接节点数多的情况。例如对于一个有M个输入端口和N个输出端口的交叉开关要增加成M输入端口和N个输出端口的交叉开关则需要增加M+N+1个交叉点。四核龙芯号处理器的设计即采用交叉开关来互连处理器核和共享二级Cache体。
**3.片上网络**
针对传统互连结构的局限C. Seitz和W. Dally在21世纪初首先提出了片上网络的概念。图\@ref(fig:noc-example)中有6个处理器核节点连接到网络中P0\~P5当节点P2与P5进行数据通信时它首先发送一个带有数据包的消息到网络中然后网络将这个消息传输给P5。片上网络借鉴了分布式网络的TCP/IP协议传输数据的方式将数据封装成数据包通过路由器之间的分组交换和对应的存储-转发机制来实现处理器核间的通信。在片上网络中片上多核处理器被抽象成节点、互连网络、网络接口Network Interface等元素。片上网络研究内容主要包括拓扑结构、路由算法、流量控制Flow Control、服务质量等。
```{r noc-example, fig.cap='片上网络示意图', fig.align='center', echo = FALSE, out.width='80%'}
knitr::include_graphics('./images/chapter11/noc_example.png')
```
**1拓扑结构。**片上网络是由节点和传输信道的集合构成的。片上网络的拓扑是指网络中节点和信道的排列方式。环Ring、网格Mesh拓扑结构为最常见的两种。如图\@ref(fig:topo)所示Mesh拓扑结构中包含16个节点编号为0到15每个节点与4条边相连但因为图中所示的边是双向的每一条边可以看作两条方向相反的有向边因此图中每个节点实际上是与8条信道线路相连。IBM CELL处理器和Intel SandyBridge处理器采用环连接Tilera公司的Tile64处理器采用Mesh互连。
```{r topo, fig.cap='拓扑结构示意图', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/topo.png')
```
**2路由算法。**片上网络所采用的路由方法决定了数据包从源节点到目的节点的传输路径。路径是传输信道的集合,即$P = {c_1,c_2,…c_k}$, 其中当前信道$c_i$的输出节点与下一跳信道$c_{i+1}$的输入节点相同。在某些片上网络拓扑结构中如环从某个源节点出发到目的节点的路径只有唯一的一条对于某些片上网络拓扑结构来说如Mesh可能有多条路径。
路径的选择可以遵循很多原则针对如Mesh这样的网络拓扑结构最常见的最短路径选择有两种
- 维序路由Dimension-Order Routing,简称DOR。这是最简单、最直接的最短路径路由它的策略是首先选择一个维度方向传输当此维度走到目的地址相同维度方向后再改变到其他维度。比如对于网格结构的拓扑路径的选择可以是先沿X方向水平方向走到与目的地址一致的列再选择Y方向竖直方向
- 全局自适应路由Adaptive Routing。这是为了解决局部负载不均衡的情况而产生的路由方法简单来说就是在每个节点有多种方向选择时优先选择负载较轻的那一个节点方向作为路径。
**3路由器结构。**路由器由寄存器、交叉开关、功能单元和控制逻辑组成这些部件共同实现了路由计算和流控制为了存储和转发流控单元flit到它们的目的地节点所需的控制功能。这里主要介绍经典的路由器结构。图\@ref(fig:router-struct)所示为一个适用于Mesh结构的路由器结构。节点的每一个输入端口都有一个独立的缓冲区Buffer在数据包可以获得下一跳资源离开之前缓冲区将它们存储下来。交叉开关连接输入端的缓冲区和输出端口数据包通过交叉开关控制传输到它指定的输出端口。分配器包括路由计算、虚通道分配和交叉开关分配三种功能路由计算用来计算head flit的下一跳输出方向虚通道分配用来分配flit在缓冲队列的位置交叉开关分配用来仲裁竞争的flit中哪个可以获得资源传输到输出端口。
```{r router-struct, fig.cap='路由器结构图', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/router_struct.png')
```
**4流量控制。**流量控制用来组织每个处理器核节点中有限的共享资源片上网络的主要资源就是信道Channel和缓冲区Buffer。信道主要用来传输节点之间的数据包。缓冲区是节点上的存储装置比如寄存器、内存等它用来临时存储经过节点的数据包。当网络拥塞时数据包需要临时存在缓冲区中等待传输。为了充分实现拓扑结构和路由方法的性能当有空闲的信道可以使用时流量控制必须尽量避免资源的冲突发生。好的流量控制策略要求它保持公平性和无死锁不公平的流量控制极端情况会导致某些数据包陷入无限等待状态死锁是当一些数据包互相等待彼此释放资源而造成的无限阻塞的情况。片上网络为了可以有效执行一定要是无死锁的。
下面以经典的基于信用的流量控制为例介绍片上网络中流量控制方法。如图\@ref(fig:flow-control-method)所示每一个处理器核节点的输入端口有自己的缓冲区队列分别用来存取来自对应的上一跳节点的数据比如i+1号节点最左侧的Buffer用来存储来自i号节点的数据包。同时每个节点上对应其相邻的节点都有一个计数器分别是S[0]~S[3]用来记录相邻节点内缓冲区Buffer的使用情况。
举例来说对于处理器核节点i的每一个计数器的初始状态S[0-3]都设为0当它向相邻节点如i+1号节点发送flit时首先判断S[0]的值是否已达到Buffer的最大值如果没有则将S[0]的值加1然后将flit发送过去如果S[0]已经达到最大值则数据会被扣留在Buffer中直到右侧节点有足够的空间收留来自它的数据。同时对于i+1号节点每当它左侧的Buffer送走一个flit时它就向其左侧的节点发送一个Credit信号通知左侧节点此Buffer已多出一个空余位置当左侧节点收到此Credit信号后则会更新对应的S[0]减1。整个流程如图\@ref(fig:flow-control-method)所示。
```{r flow-control-method, fig.cap='基于信用的流量控制', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/flow_control_method.png')
```
## 多核处理器的同步机制
在介绍多核处理器的同步机制之前先来看一个同步问题的例子。有两个处理器核P0和P1分别对同一共享地址的变量A进行加1的操作。于是处理器P0先读取A的值然后加1最后将A写回内存。同样处理器核P1也进行一样的操作。然而如图\@ref(fig:different-results)所示实际的运算过程却有可能产生两种不一样的结果注意整个运算过程是完全符合Cache一致性协议规定的。所以A的值可能增加了1如图\@ref(fig:different-results)a所示也可能增加了2如图\@ref(fig:different-results)b所示。然而这样的结果对于软件设计人员来说是完全无法接受的。因此需要同步机制来协调多个处理器核对共享变量的访问。
```{r different-results, fig.cap='一个并行程序产生两种不同结果', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/different_results.png')
```
为了解决同步问题需要采用同步机制。常见的同步机制包括锁操作、栅障操作和事务内存。锁操作和事务内存主要用于保护临界区栅障操作用于实现全局同步。锁操作和栅障操作属于传统同步方法广泛用于并行系统中事务内存则是适应多核处理器设计需求的一种新同步机制。同步机制一般建立在用户级软件例程Routine而这些软件例程主要基于硬件提供的同步指令来实现。
**1.原子操作**
硬件设计人员在处理器中增加了一种特殊的机制支持多个操作之间的原子性Atomic也就是不可分割性。在硬件上实现满足不可分割性的原子操作有许多种方法既可以在寄存器或者存储单元中增加专门的硬件维护机制也可以在处理器的指令集中添加特定的原子指令。早期的处理器大多选择在存储单元中增加特殊的原子硬件维护机制而现代处理器大多使用原子指令方式。原子指令的实现方式可以分为两种其中一种是直接使用一条“读-改-写”Read-Modify-WriteRMW原子指令来完成另一种是使用一组原子指令对LL/SCLoad-Linked/Store-Conditional来完成指定的原子操作。
常见的“读-改-写”原子指令包括Test_and_Set、Compare_and_Swap、Fetch_and_Op等。Test_and_Set 指令取出内存中对应地址的值同时对该内存地址赋予一个新的值。Compare_and_Swap指令取出内存中对应地址的值和另一个给定值A进行比较如果相等则将另一个给定值B写入这个内存地址否则不进行写操作;指令应返回状态例如X86的cmpxchg指令设置eflags的zf位来指示是否进行了写操作。Fetch_and_Op指令在读取内存对应地址值的同时将该地址的值进行一定的运算再存回。根据运算操作Op的不同Fetch_and_Op指令又有许多种不同的实现形式。例如Fetch_and_Increment指令就是读取指定地址的值同时将该值加1并写回内存。可以看出“读-改-写”原子指令和内存的交互过程至少有两次,一次读内存,另一次写内存,而两次交互过程之间往往还有一些比较、加减之类的运算操作(改)。
使用原子指令对LL/SC实现原子操作方式的过程如下首先LL指令将对应地址的内存数据读入寄存器然后可以对该寄存器中的值进行任意的运算最后使用SC指令尝试将运算后的数据存回内存对应的地址。当且仅当LL指令完成之后没有其他对该地址内存数据的修改操作则SC指令执行成功并返回一个非零值运算后的数据顺利写回内存否则SC指令执行失败并返回值0修改后的数据不会被写回内存也不会产生任何对内存的改动。SC指令失败后一般需要重新执行上述过程直到SC指令成功为止。SC指令的成功说明了LL/SC指令之间没有其他对同一地址的写入操作也就保证了LL/SC指令之间的不可分割性。图\@ref(fig:ll-sc-atomic)的例子采用LL/SC指令实现了寄存器R1的内容与R3对应的内存位置的内容的原子交换。
```{r ll-sc-atomic, fig.cap='用LL/SC指令对实现原子交换操作', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/ll_sc_atomic.png')
```
LL/SC原子指令对的优点在于设计简单每条指令只需和内存交互一次且在LL指令和SC指令之间可以加入任意的运算指令可以灵活地实现类似于“读-改-写”的复杂原子操作。其缺点在于密集共享时SC不容易成功一种优化措施是LL访问时把相应Cache行置为EXC状态而不是SHD状态这样可以提高SC成功的概率。相对于Test_and_Set指令和Fetch_and_Op指令等实现复杂的单条原子指令LL/SC指令对成为目前最常见的原子指令被多种现代RISC指令系统所采用如MIPS、IBM Power、DEC Alpha和LoongArch等等。
**2.锁的软件实现方法**
Lock是并行程序中常用的对多个线程共享的临界区Critical Section进行保护的同步操作。自旋锁Spin Lock是锁操作的一种最基本的实现形式。Test_and_Set自旋锁是最简单的自旋锁通过使用Test_and_Set原子指令来完成锁的获取、等待和查询。Test_and_Set锁的基本步骤如图\@ref(fig:test-and-set)所示假设1表示锁被占用0表示锁空闲。处理器使用Test_and_Set原子指令读取锁变量的值同时将锁变量的值改为1。如果读取到锁的值为0说明锁空闲该处理器成功获得锁。由于Test_and_Set指令已经将锁的值同时改为了1所以其他处理器不可能同时获得这把锁。如果锁的值为1说明已经有其他处理器占用了这把锁则该处理器循环执行Test_and_Set指令自旋等待直到成功获得锁。由于当时锁的值已经是1了Test_and_Set指令再次将锁的值设为1实际上锁的值并没有发生变化所以不会影响到锁操作的正确性。当获得锁的处理器打算释放锁时只需要简单地执行一条普通的store指令将锁的值设置为0即可。由于一次只能有一个处理器核获得锁所以不用担心多个处理器核同时释放锁而引发访存冲突也就不需要使用原子指令来释放锁了。
```{r test-and-set, fig.cap='Test-and-Set自旋锁', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/test_and_set.png')
```
Test_and_Set自旋锁最主要的一个缺点就是对锁变量的访存冲突。当一个处理器核获得锁以后其他等待的处理器核会不断循环执行Test_and_Set指令访问锁变量试图获取锁权限从而在片上互连上产生大量的访存通信。一种简单的优化方法就是在Test_and_Set指令之间加入一定的延迟减少等待阶段Test_and_Set原子指令自旋执行的次数以减轻访存的压力。此外研究人员还提出了排队锁Ticket Lock、基于数组的队列锁Array-Based Queuing Lock、基于链表的队列锁List-Based Queuing Lock等优化机制。
**3.栅障软件实现方法**
栅障Barrier是并行程序中常用的同步操作。栅障要求处理器核等待一直到所有处理器核都到达栅障后才能释放所有处理器核继续执行。栅障有多种实现方式下面主要介绍比较简单的集中式栅障。集中式栅障就是在共享存储中设置一个共享的栅障变量。每当一个处理器核到达栅障以后就使用原子指令修改栅障值表示自己已经到达如将栅障的值加1然后对该栅障值进行自旋等待如图\@ref(fig:centralized-barrier)的伪代码所示。当栅障的值表明所有处理器核都已经到达(即栅障的值等于预计到达的总的处理器核的数量)时,栅障操作顺利完成,所有自旋等待的处理器核就可以继续往下执行了。集中式栅障的实现简单、灵活,可以支持各种类型的栅障,包括全局栅障和部分栅障,适用于可变处理器核数量的栅障操作。
在集中式栅障中每一个到达的处理器核都需要对同一个共享的栅障值进行一次修改以通告该处理器核到达栅障已到达栅障的处理器核会不断访问栅障值以判断栅障是否完成。由于Cache一致性协议的作用这个过程会在片上互连上产生许多无用的访存通信并且随着处理核数的增加栅障的时间和无用的访存数量都会快速增长所以集中式栅障的可扩展性不好。为了减少上述查询和无效的访存开销集中式栅障也可以采用类似于Test_and_Set锁的方式在查询操作之中增加一些延迟。加入延迟虽然可以减少一些网络带宽的浪费但是也可能降低栅障的性能。针对集中式栅障的弱点研究人员提出了软件合并树栅障等优化方法。
```{r centralized-barrier, fig.cap='集中式栅障伪代码', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/centralized_barrier.png')
```
**4.事务内存**
1993年Herlihy和Moss以事务概念为基础针对多核处理器中并行编程的同步效率问题提出了事务内存的概念。在事务内存中访问共享变量的代码区域声明为一个事务Transaction。事务具有原子性Atomicity即事务中的所有指令要么执行要么不执行一致性Consistency即任何时刻内存处于一致的状态隔离性Isolation即事务不能看见其他未提交事务涉及的内部对象状态。事务执行并原子地提交所有结果到内存如果事务成功或中止并取消所有的结果如果事务失败
事务内存实现的关键部分包括:冲突检测、冲突解决以及事务的提交和放弃。冲突检测就是确定事务并发执行过程中是否存在数据的冲突访问。冲突解决是指在发生冲突时决定继续或者放弃事务的执行。如果支持事务的暂停操作,可以暂停引起冲突的事务,直到被冲突的事务执行结束;如果不支持事务的暂停操作,就必须在引起冲突的事务中选择一个提交,同时放弃其他事务的执行。事务的提交或放弃是解决事务冲突的核心步骤,事务提交需要将结果数据更新到内存系统中,事务放弃需要将事务的结果数据全部丢弃。
事务内存实现方式主要有软件事务内存和硬件事务内存两种。软件事务内存在通过软件实现不需要底层硬件提供特殊的支持主要以库函数或者编程语言形式实现。例如RSTM、DSTM、Transactional Locking等以库函数实现线程访问共享对象时通过对应的库函数来更新事务执行的状态、检测冲突和处理等HSTM语言中扩展了事务原语AtomCaml在ObjectCaml语言中增加了对事务内存同步模型的支持等。硬件事务内存主要对多核处理器的Cache结构进行改造主要包括增加特定指令来标示事务的起止位置使用额外的事务Cache来跟踪事务中的所有读操作和写操作扩展Cache一致性协议来检测数据冲突。软件事务内存实现灵活更容易集成到现有系统中但性能开销大硬件事务内存需要修改硬件但是性能开销小程序整体执行性能高。Intel Haswell处理器和IBM Power8处理器中实现了对硬件事务内存的支持。下面来看一个具体的实现例子。
Intel TSXTransactional Synchronization Extensions是Intel公司针对事务内存的扩展实现提出了一个针对事务内存的指令集扩展主要包括3条新指令XBEGIN、XEND和XABORT。XBEGIN指令启动一个事务并提供了如果事务不能成功执行的回退地址信息XEND指令表示事务的结束XABORT指令立刻触发一个中止类似于事务提交不成功。硬件实现以Cache行为单位跟踪事务的读集Read-Set和写集Write-Set。如果事务读集中的一个Cache行被另一个线程写入或者事务的写集中的一个Cache行被另一个线程读取或写入则事务就遇到冲突Conflict通常导致事务中止。Intel Haswell处理器中实现了Intel TSX。
## 典型多核处理器
### 龙芯3A5000处理器
龙芯3A5000于2020年研制成功是龙芯中科技术股份有限公司研发的首款支持龙芯自主指令集LoongArch的通用多核处理器主要面向桌面计算机和服务器应用。龙芯3A5000片内集成4个64位LA464高性能处理器核、16MB的分体共享三级Cache、2个DDR4内存控制器支持DDR4-3200、2个16位HTHyperTransport控制器、2个I2C、1个UART、1个SPI、16路GPIO接口等。龙芯3A5000中多个LA464核及共享三级Cache模块通过AXI互连网络形成一个分布式共享片上末级Cache的多核结构。采用基于目录的Cache一致性协议来维护Cache一致性。另外龙芯3A5000还支持多片扩展将多个芯片的HT总线直接互连就形成更大规模的共享存储系统最多可支持16片互连
LA464是支持Loongarch指令集的四发射64位高性能处理器核具有256位向量部件。LA464的结构如图\@ref(fig:la464-uarch)所示主要特点如下四发射超标量结构具有四个定点、两个向量、两个访存部件支持寄存器重命名、动态调度、转移预测等乱序执行技术每个向量部件宽度为256位可支持8个双32位浮点乘加运算或4个64位浮点运算一级指令Cache和数据Cache大小各为64KB4路组相联牺牲者CacheVictim Cache作为私有二级Cache大小为256KB16路组相连支持非阻塞Non-blocking访问及装入猜测Load Speculation等访存优化技术支持标准的JTAG调试接口方便软硬件调试。
```{r la464-uarch, fig.cap='LA464处理器核结构', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/la464_uarch.png')
```
龙芯3A5000芯片整体架构基于多级互连实现结构如图\@ref(fig:ls3a5000-arch)所示(图\@ref(fig:ls3a5000-layout)为芯片版图。第一级互连采用5x5的交叉开关用于连接四个LA464核作为主设备、四个共享Cache模块作为从设备、以及一个IO端口连接IO-RING。IO端口使用一个Master和一个Slave。第二级互连采用5x3的交叉开关连接4个共享Cache模块作为主设备两个DDR3/4内存控制器、以及一个IO端口连接IO-RING。IO-RING连接包括4个HT控制器MISC模块SE模块与两级交叉开关。两个HT控制器lo/hi共用16位HT总线作为两个8位的HT总线使用也可以由lo独占16位HT总线。HT控制器内集成一个DMA控制器DMA控制器负责IO的DMA控制并负责片间一致性的维护。上述互连结构都采用读写分离的数据通道数据通道宽度为128bit与处理器核同频用以提供高速的片上数据传输。此外一级交叉开关连接4个处理器核与Scache的读数据通道为256位以提高片内处理器核访问Scache的读带宽。
龙芯3A5000主频可达2.5GHz,峰值浮点运算能力达到160GFLOPS。
```{r ls3a5000-arch, fig.cap='龙芯3A5000芯片结构', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/ls3a5000_arch.png')
```
```{r ls3a5000-layout, fig.cap='龙芯3A5000的版图', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/ls3a5000_layout.png')
```
### Intel SandyBridge架构
Intel SandyBridge架构于2011年推出是Intel面向32nm工艺的新架构它是Core处理器架构的第二代架构。根据面向移动、桌面还是服务器应用有支持2~8核的不同处理器产品。
SandyBridge处理器主要包括五个组成部分处理器核、环连接Ring Interconnect、共享的三级Cache、系统代理System Agent和图形核心GPU。图\@ref(fig:sandybridge-arch)为Sandybridge处理器的结构示意图。它的处理器核心采用乱序执行技术支持双线程支持AVX向量指令集扩展。系统代理包括内存控制器、功耗控制单元Power Control Unit、PCIE接口、显示引擎和DMI等。存储层次包括每个核私有的一级和二级Cache、多核共享的LLC三级Cache。LLC分体实现在处理器核和图形核心、系统代理之间共享。
SandyBridge采用环连接来互连处理器核、图形核心、LLC和系统代理。环连接由请求Request、响应Acknowledge、侦听Snoop、数据Data四条独立的环组成。这四条环采用一个分布式的通信协议维护数据一致性和序Ordering实现了基于侦听的Cache一致性协议。环连接采用完全流水线设计以核心频率运行随着连接的节点数目增加带宽也随之增加在处理器核总数不太大的情况下有较好的伸缩性。另外由于环连接传递的消息具有天然的序使得Cache一致性协议的设计和验证比较简单。如图\@ref(fig:sandybridge-arch)所示SandyBridge的环有6个接口包括4个处理器核和三级Cache共享的接口一个图形核心的接口和1个系统代理的接口。
4核SandyBridge处理器的主频达到3GHz支持128位向量处理峰值性能达到96GFLOPS理论访存带宽达到25.6GB/s采用Stream测试程序集实测的访存带宽为14GB/s~16GB/s。
```{r sandybridge-arch, fig.cap='SandyBridge结构示意图', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/sandybridge_arch.png')
```
### IBM Cell处理器
Cell处理器由IBM、索尼和东芝联合研发并在2005年国际固态电路会议ISSCC上首次公开主要面向游戏、超级计算等领域。图\@ref(fig:cell-arch)为Cell处理器的结构示意图。Cell采用异构多核架构它由1个相对比较简单的支持同时双线程并行的双发射64位PowerPC内核称为PPE和8个SIMD型向量协处理器称为SPE构成。由一个高带宽的片上环状高速总线将PPE、SPE、RAM内存总线接口控制器BIC、FlexIO外部总线接口控制器连接起来。PPE主要负责控制并运行操作系统SPE完成主要的计算任务。SPE的SIMD执行部件是128位宽的从而可在一个时钟周期里完成4个32位的定点或浮点乘加运算。SPE里内置了256KB的SRAM作为局部存储器Local Storage简称LSLS与内存间的通信必须通过DMA 进行。SPE配置了较大的寄存器堆128个128位的寄存器来尽量减少对内存的访问。由于SPE不采用自动调配数据的Cache机制需要显式地将内存中的数据先搬到LS中供SPE计算为了减少数据搬运需要依赖高水平程序员或编译器的作用来获得高性能编程较为困难。
Cell处理器可在4GHz频率下工作峰值浮点运算速度为256GFLOPS理论访存带宽为25.6GB/s。由于存在编程及推广困难等原因目前Cell处理器已经停止研发。
```{r cell-arch, fig.cap='IBM Cell结构示意图', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/cell_arch.png')
```
### NVIDIA GPU
GPUGraphics Processing Unit是进行快速图形处理的硬件单元现代GPU包括数百个并行浮点运算单元是典型的众核处理器架构。本节主要介绍NVIDIA公司的Fermi GPU体系结构。
第一个基于Fermi体系结构的GPU芯片有30亿个晶体管支持512个CUDA核心组织成16个流多处理器Stream Multiprocessor简称SM。SM结构如图\@ref(fig:sm-single)、\@ref(fig:sm-whole)所示。每个SM包含32个CUDA核心Core、16个load/store单元LD/ST、4个特殊处理单元Special Function Unit简称SFU、64KB的片上高速存储。每个CUDA核心支持一个全流水的定点算术逻辑单元ALU和浮点单元FPU如图\@ref(fig:cuda-core)所示每个时钟周期可以执行一条定点或者浮点指令。ALU支持所有指令的32位精度运算FPU实现了IEEE 754-2008浮点标准支持单精度和双精度浮点的融合乘加指令Fused Multiply-Add, 简称FMA。16个load/store单元可以每个时钟周期为16个线程计算源地址和目标地址实现对这些地址数据的读写。SFU支持超越函数的指令如sin、cos、平方根等。64KB片上高速存储是可配置的可配成48KB的共享存储和16KB一级Cache或者16KB共享存储和48KB一级Cache。片上共享存储使得同一个线程块的线程之间能进行高效通信可以减少片外通信以提高性能。
```{r sm-single, fig.cap='单个Fermi流多处理器结构图', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/sm_single.png')
```
```{r sm-whole, fig.cap='Fermi流多处理器整体结构图', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/sm_whole.png')
```
```{r cuda-core, fig.cap='CUDA核结构', fig.align='center', echo = FALSE, out.width='30%'}
knitr::include_graphics('./images/chapter11/cuda_core.png')
```
1.Fermi的线程调度
Fermi体系结构使用两层分布式线程调度器。块调度器将线程块Thread Block调度到SM上 SM以线程组Warp为单位调度执行每个Warp包含32个并行线程这些线程以单指令多线程Single Instruction Multi Thread,简称SIMT的方式执行。SIMT类似于SIMD表示指令相同但处理的数据不同。每个SM有两个Warp调度器和两个指令分派单元允许两个Warp被同时发射和并发执行。双Warp调度器Dual Warp Scheduler选择两个Warp从每个Warp中发射一条指令到一个16个核构成的组、16个load/store单元或者4个SFU单元。大多数指令是能够双发射的例如两条定点指令、两条浮点指令或者是定点、浮点、load、store、SPU指令的混合。双精度浮点指令不支持与其他指令的双发射。
2.Fermi存储层次
Fermi体系结构的存储层次由每个SM的寄存器堆、每个SM的一级Cache、统一的二级Cache和全局存储组成。图\@ref(fig:fermi-mem-hierarchy)为Fermi存储层次示意图。具体如下:
1寄存器。每个SM有32K个32位寄存器每个线程可以访问自己的私有寄存器,随线程数目的不同每个线程可访问的私有寄存器数目在21~63间变化。
2一级Cache和共享存储。每个SM有片上高速存储主要用来缓存单线程的数据或者用于多线程间的共享数据可以在一级Cache和共享存储之间进行配置。
3L2 Cache。768KB统一的二级Cache在16个SM间共享服务于所有到全局内存中的load/store操作。
4全局存储。所有线程共享的片外存储。
Fermi体系结构采用CUDA编程环境可以采用类C语言开发应用程序。NVIDIA将所有形式的并行都定义为CUDA线程将这种最底层的并行作为编程原语编译器和硬件可以在GPU上将上千个CUDA线程聚集起来并行执行。这些线程被组织成线程块以32个为一组Warp来执行。Fermi体系结构可以看作GPU与CPU融合的架构具有强大的浮点计算能力除了用于图像处理外也可作为加速器用于高性能计算领域。采用Fermi体系结构的GeForce GTX 480包含480核主频700MHz单精度浮点峰值性能为1.536TFLOPS访存带宽为177.4GB/s。
```{r fermi-mem-hierarchy, fig.cap='Fermi的存储层次图', fig.align='center', echo = FALSE, out.width='50%'}
knitr::include_graphics('./images/chapter11/fermi_mem_hierarchy.png')
```
### Tile64处理器
Tile64是美国Tilera公司于2007年推出的64核处理器主要面向网络和视频处理等领域。图\@ref(fig:tile64-arch)为Tile64处理器的结构图。Tile64具有64个Tile瓦片组成8*8的Mesh结构每个Tile包含通用CPU核、Cache和路由器。Tile64的处理器核支持MIPS类VLIW指令集采用三发射按序短流水线结构支持两个定点功能部件和1个load/store访存部件。在互连结构方面Tile64采用Mesh互连结构通过路由器实现了5套低延迟的、不同用途的Mesh互连网络提供了足够的通信带宽。在访存结构方面每个Tile拥有私有一级Cache16KB和私有二级Cache64KB以及虚拟的三级Cache所有Tile的二级Cache聚合。Tile64采用邻居Neighborhood缓存机制实现片上分布式共享Cache每个虚拟地址对应一个HomeTile先访问该HomeTile的私有Cache如果不命中则访问内存数据只在它的Home Tile的私有Cache中缓存由Home Tile负责维护数据一致性。Tile64支持4个DDR2内存控制器2个10Gbit的以太网接口2个PCIE接口及其他一些接口。Tile64的运行主频为1GHz峰值性能为每秒192G个32位运算理论访存带宽为25GB/s。
```{r tile64-arch, fig.cap='Tile64处理器结构图', fig.align='center', echo = FALSE, out.width='100%'}
knitr::include_graphics('./images/chapter11/tile64_arch.png')
```
## 本章小结
可以从以下几个维度对多核处理器结构进行分析一是从处理器核及访存带宽的维度包括核的数量、大核还是小核、同构核还是异构核、通用核还是专用核等访存带宽与峰值计算能力之间的比例决定该多核处理器的通用性。二是从存储一致性模型的维度存储一致性模型对多个处理器核发出的访存指令次序进行约定包括顺序一致性模型、处理器一致性模型、弱一致性模型等。三是从Cache组织及一致性协议的维度包括有几级Cache、Cache容量、私有还是共享CacheCache一致性协议是把一个处理器核新写的值传播到其他处理器核的一种机制。四是从片上互连结构的维度即多个核处理器核间如何实现通信。五是多核之间的同步机制的维度如互斥锁操作lock、路障操作barrier等。
## 习题
1. 关于多核处理器的Cache结构请介绍UCA与NUCA的特点。
2. 有两个并行执行的线程在顺序一致性和弱一致性下它各有几种正确的执行顺序给出执行次序和最后的正确结果假设X、Y的初始值均为0
```{r parallel-example, echo = FALSE}
dt <- data.frame('P1' = 'X=1;\nprint Y;',
'P2' = 'Y=1;\nprint X;')
flextable(dt) %>%
width(width=2.0) %>%
theme_box()
```
3. 关于Cache一致性协议MESI协议比ESI协议增加了M状态请解释有什么好处。
4. 请分别采用Fetch_and_Increment和Compare_and_Swap原子指令编写实现自旋锁的代码并分析可能的性能改进措施。
5. 在共享存储的多处理器中经常会出现假共享现象。假共享是由于两个变量处于同一个Cache行中引起的会对性能造成损失。为了尽量减少假共享的发生程序员在写程序时应该注意什么
6. 请介绍片上网络路由器设计中的虚通道概念,并说明采用虚通道有什么好处。
7. 分析Fermi GPU的存储结构指出不同层次存储结构的带宽、延迟、是否共享。
\newpage