Parallel Architecture Lecture3
Parallel Architecture Lecture3
Flynn 分类
概念
指令流/数据流分类法,即费林-Flynn分类法。
指令流(Instruction Stream):机器执行的指令序列。
数据流(Data Stream):指令调用的数据序列,包括输入数据和中间结果。
多倍性(Multiplicity):在系统性能瓶颈部件上同时处于同一执行阶段的指令或数据的最大可能个数。
四类计算机
两个独立的维度——指令流和数据流的不同组织方式,将计算机系统分为四类:
单指令单数据流(Single Instruction stream and Single Data stream , SISD)
SISD系统是一种传统的顺序执行的单处理器计算机,它的硬件不支持任何形式的并行计算,所有的指令都是串行执行。在任何一个时钟周期内,CPU只能处理一个数据流, 因此这种机器被称作单指令流单数据流机器。
单指令多数据流(Single Instruction stream and Multiple-Data stream , SIMD)
SIMD系统有多个处理单元,由单一的指令部件控制,按照同一指令流的要求为它们分配各不相同的数据流并进行处理。系统结构由一个控制器、多个处理器、多个存贮模块 和一个互连总线(网络)组成。
多指令单数据流(Multiple-Instruction stream and Single Data stream , MISD)系 统
MISD系统有多个处理单元,每个处理单元按照多条不同的指令要求同时对同一数据流及其处理输出的结果进行不同的处理,把一个单元的输出作为另一个单元的输入。
多指令多数据流(Multiple-Instruction stream and Multiple-Data stream , MIMD)
MIMD系统又称为多处理机系统,是指能实现指令、数据作业、任务等各级全面并行计算的多机处理系统,可以将一个主任务分解为众多子任务并行执行以缩短工作时间。
SIMD v.s. MIMD
SIMD的各个处理器同步运行,即分别在来自存储器的不同数据流上并行执行相同的指令流,也就是说有一个指令流和多个数据流。SIMD的各个处理器同步使用连接网络。
MIMD的各个处理器异步运行,即在各自的数据流上执行自己的指令流,也就是说有多个指令流和多个数据流。MIMD的各个处理器异步使用连接网络。
SIMD | MIMD | |
---|---|---|
控制器 | 一个 | 多个 |
处理器 | 多个 | 多个 |
存储部件 | 多个 | 多个 |
连接网络 | 有 | 有 |
并行计算机基本属于MIMD范畴。
SIMD
在SIMD中可以同时对多个数据元素执行单个操作。人们有时会用向量化(vectorization)来表达对SIMD等向量化指令的使用:
不使用SIMD
function simple_sum(A)
rst = zero(eltype(A))
for x in A
rst += x
end
return rst
end
使用SIMD
function simd_sum(A)
rst = zero(eltype(A))
@simd for x in A
rst += x
end
return rst
end
@simd宏用于告诉Julia:请尽量将这 段代码编译为SIMD的版本。
运行评估
A = rand(64, 64);
@btime simple_sum(A);
@btime simd_sum(A);
simd版本的运行时间数倍小于不使用simd的。
SIMD加速比估计
SIMD 的加速比取决于数据类型的位宽和 CPU 的架构。
大部分 CPU 支持 256 位 SIMD,即一次向量化运算的总位宽:
# 4xFloat64=256位宽(4个64位浮点数)
A = rand(Float64, 64, 64);
@btime simple_sum(A);
@btime simd_sum(A);
# 8xFloat32=256位宽(8个32位浮点数)
A = rand(Float32, 64, 64);
@btime simple_sum(A);
@btime simd_sum(A);
几乎所有的CPU均不支持16位浮点运算。
SIMD的限制
即使加了 @simd 宏,也不一定会编译出带 SIMD 的版本:
function simple_dot(A, B)
@assert axes(A) == axes(B)
rst = zero(eltype(A))
for i in eachindex(A)
rst += A[i] * B[i]
end
return rst
end
function simd_dot(A, B)
@assert axes(A) == axes(B)
rst = zero(eltype(A))
@simd for i in eachindex(A)
rst += A[i] * B[i]
end
return rst
end
A, B = rand(4096), rand(4096);
@btime simple_dot(A, B);
@btime simd_dot(A, B);
带有simd宏会消耗更长时间,下面列出@simd的几种限制:
SIMD 代码中不允许条件分支,即:if 判断或 XXX?AAA:BBB 三元运算符均不允许出现:
function simple_dot_if(A, B)
@assert axes(A) == axes(B)
rst = zero(eltype(A))
@inbounds for i in eachindex(A)
a, b = A[i], B[i]
if a * b > 0.5
rst += a + b
end
end
return rst
end
# 不支持simd
function simd_dot_if(A, B)
@assert axes(A) == axes(B)
rst = zero(eltype(A))
@inbounds @simd for i in eachindex(A)
a, b = A[i], B[i]
if a * b > 0.5
rst += a + b
end
end
return rst
end
# 支持simd(用的是ifelse函数而非分支,区别见下)
function simd_dot_ifelse(A, B)
@assert axes(A) == axes(B)
rst = zero(eltype(A))
@inbounds @simd for i in eachindex(A)
a, b = A[i], B[i]
rst += ifelse(a*b>0.5, a+b, zero(rst))
end
return rst
end
ifelse是一个无分支形式的条件判断函数。它与正常的if-else分支 的差别在于:ifelse两种情况的值都会计算,而if-else会根据条件选择性地计算一种情况下的值。 这本身代表一种优化思路:引入额外计算来消除分支,从而带来潜在的性能优化。
只有非常有限的基本指令支持SIMD,如:四则运算、位运算。
function simple_sum_mod(A)
rst = zero(eltype(A))
for x in A
rst += x % 3
end
return rst
end
# 带模求和
function simd_sum_mod(A)
rst = zero(eltype(A))
@simd for x in A
rst += x % 3
end
return rst
end
# 优化思路:通过引入一些合理的限制和假设,不支持SIMD的运算可以转换为SIMD友好的运算:
function mod3_lookup(n)
lookup = Vector{Int}(undef, n)
for i in 1:n
lookup[i] = i % 3
end
return lookup
end
# 先把取模运算算好存上,simd部分只需要找出来并加上即可。
const LOOKUP_MOD3 = mod3_lookup(255)
function simd_sum_mod_lookup(A)
rst = zero(eltype(A))
@simd for x in A
rst += @inbounds LOOKUP_MOD3[x]
end
return rst
end
在Kunpeng920上使用SIMD
原理知识
SIMD(Single Instruction Multi Data)单指令多数据流,支持将数据打包在一个大型寄存 器中进行处理的一组指令集,在性能上相比标量操作吞吐量更大,适合数据密集型的操作;
Kunpeng 920支持ARMv8.2A指令集,有32个128bit的向量寄存器,流水线中有2个FSU单 元可进行FP/ASIMD操作。
使用方法
编译器自动向量化优化,加入-ftree-vectorize,或编译优化设置为-O3,可添加-ftree vectorizer-verbose参数查看编译器优化;使用Neon Instrinsics改写循环体,优化处理过程;引入OpenMP,使用OpenMP的关键词#pragma omp simd协助SIMD优化。
注意
Kunpeng 920上使用SIMD优先推荐对整形/单精度浮点数据进行改写,长整形数据和双精度 浮点类型效果相对不明显;
针对短循环或者简单循环不建议进行SIMD优化,SIMD向量寄存器的LD/ST打包解包过程也 会带来一定开销,短循环可能还会带来性能下降。
总结
SIMD 是CPU指令集并行:它可以带来近乎免费的性能加速,但使用条件比较严格。
一般 SIMD 仅发生在底层基础代码:高层代码几乎不可能满足 SIMD 的要求。
Julia注意事项
@simd 宏只在确实有效的时候才添加
@inbounds 宏只在确定下标不会越界,并且能够带来可观性能优势的时候才添加
@simd 只可能出现在嵌套 for 循环的最内层循环
通过性能对比(@btime)来验证 @simd 的加速效果
通过 @code_llvm 来检查生成的代码中 SIMD 是否生效
SIMD.jl允许手动的 SIMD 代码编写:对于特定底层算法实现可以带来更激进的优化策略
LoopVectorization.jl 提供 @turbo 宏:包含 AVX 以及 for 循环优化等综合性手段
内存访问模型
并行机体系结构
组成要素
处理器(processor):计算单元
互联网络(interconnetct network): 连接
内存(memory):多个存储模块组成
并行机的基本特征是具备多个计算单元和存储模块,各个模块通过互联网络耦合。根据耦合的紧密程度可分为紧耦合和松耦合。不同的并行计算机,其各模块耦合的松紧程度可以有区别。
共享内存和分布式内存
共享内存的多处理机 (multiprocessors)体系
通过总线共享一套内存系统(各个内存模块虽然物理上独立,但是逻辑上统一编址)
不可靠,可扩展性差。
通常也称为紧密耦合多处理机,它具有一个所有处理器都可以访问的全局物理内存。
共享内存系统的特性有:
对称性:系统中任何处理器都可以访问任何的内存单元和I/O设备 。
单地址空间:内存中每一个位置在整个的内存地址范围内有一个唯一的地址 。
低通信延迟:处理器间的通信可以利用共享内存来进行数据交换 。
高速缓存及其一致性:多级高速缓存可以支持数据的局部性,而其一致性可由硬件来增强。
分布式内存的多计算机 (multicomputers)体系
相当于一堆独立的计算机连起来,每个节点(每个机器)都有完全独享的内存。
分布式内存系统中处理器都有各自的内部寄存器,一个核内的内存地址对其他核不可见,只能由该处理器所访问,对于所有CPU都没有单一全局地址空间的概念,这 类的分布式计算机系统称为非远程存储访问(No-Remote Memory Access, NORMA)
访存模型
UMA(Uniform Memory Access)模型:均匀存储访问模型。
NUMA(Non-Uniform Memory Access)模型:非均匀存储访问模型。
COMA(Cache-Only Memory Access)模型:全高速缓存存储访问。
CC-NUMA(Coherent-Cache Nonuniform Memory Access)模型:高速缓存一致性非均匀存储访问模型。
NORMA(No-Remote Memory Access)模型:非远程存储访问模型。
UMA均匀存储访问(共享内存)
物理存储器被所有处理器均匀共享;
所有处理器访问任何存储字取相同的时间;
每台处理器可带私有高速缓存;
外围设备(I/O)也可以一定形式共享。
发生访存竞争时,仲裁策略平等对待每个结点, 即每个结点机会均等;
对称多处理(Symmetric Multiprocessing , SMP)使用的是微处理器和高速缓存;
并行向量处理机(Parallel Vector Processoe, PVP)使用的是高性能专门设计定制的向 量处理器(Vector Processor, VP)
PVP并行向量处理机
VP: Vector Processor SM: Shared Memory
SMP对称多处理机
内存模块和处理器对称地分布在互联网络的两侧;
内存访问属典型的均匀访问模型。
扩展性差,可靠性差,内存访问有瓶颈。
SMP的特点
对称共享存储
系统中任何处理器均可直接访问任何存储模块中的存储单元和I/O 模块,且访问的延迟、带宽和访问成功的概率是一致的。所有内存单元统一编址。各个处理器之间的地位等价, 不存在任何特权处理器。操作系统可在任意处理器上运行。
单一的操作系统映像
全系统只有一个操作系统驻留在共享存储器中,它根据各个处理器的负载情况,动态地分配各个进程到各个处理器,并保持各处理器间的负载平衡。
局部高速缓存cache及其数据一致性
每个处理器均配备局部cache,它们可以拥有独立的局部数据,但是这些数据必须与存储器中的数据保持一致。
低通信延迟
各进程通过读/写操作系统提供的共享数据缓存区来完成处理器间的通信,其延迟通常小于网络通信的延迟。
共享总线带宽
所有处理器共享总线的带宽,完成对内存模块和I/O模块的访问。
支持消息传递、共享存储并行程序设计
SMP的缺点
欠可靠
总线、存储器或操作系统失效可导致系统崩溃。
可扩展性差
由于所有处理器共享总线带宽,而总线带宽每3年才增加2倍,跟不上处理器速度和内存容量的增加步伐,因此,SMP并行机的处理器个数一般少于32 个,且只能提供每秒数百亿次的浮点运算性能。
NUMA非均匀存储访问(共享内存)
被共享的存储器在物理上是分布在所有的处理器中的,其所有本地存储器的集合就组成了全局地址空间;
处理器访问存储器时间是不一样的;
每台处理器照例可带私有高速缓存。
如果缓存一致性能够得到维护,那么也 可以称之为高速缓存一致性NUMA (Cache Coherent NUMA , CC NUMA),反之可称为高速缓存非一致 性NUMA(Non-Cache Coherent NUMA , NCC-NUMA)
CC-NUMA高速缓存一致性非均匀存储访问(共享内存)
大多数使用基于目录的高速缓存一致性协议;
保留SMP结构易于编程的优点,也改善常规SMP的可扩放性;
CC-NUMA实际上是一个分布共享存储的DSM多处理机系统;
它最显著的优点是程序员无需明确地在节点上分配数据,系统的硬件和软件开始时自动在各节点分配数据,在运行期间,高速缓存一致性硬件会自动地将数据迁移至要用到它的地方。
COMA全高速缓存存储访问(共享内存)
特例:全高速缓存存储访问(Cache-Only Memory Access, COMA)模型,COMA各个处理器节点没有存储层次结构,所有节点的高速缓存构成了全局 地址空间
NORMA非远程存储访问(分布式内存)
优点
内存可以随着CPU的数量进行扩展,增加处理器数量将使内存的大小等比例增加。
各个处理器可以无冲突地快速访问自己的内存,也不存在维护缓存一致性的开销。
成本效益上,可以使用商用、现成的处理器和网络。
缺点
程序员将要负责所有处理器间数据通信相关的细节问题。
很难从基于全局内存空间的数据结构上建立起到分布式内存管理的映射。
非一致的内存访问时间使得驻留在远程节点上的数据比节点本地数据的访问需要更长的时间。
MPP大规模并行处理机
由大规模“紧密”互连的节点组成;
内存访问属非远程存储访问模型(NORMA);
也被称为“message-passing” 系统。
优点
由数百个乃至数千个计算结点和I/O 结点组成,每个结点相对独立,并拥有一个或多个微处理器。
这些结点配备有局部cache,并通过局部总线或互联网络与局部内存模块和I/O设备相连接。
这些结点由局部高性能网卡(NIC) 通过高性能互联网络相互连接。
各个结点均拥有不同的操作系统映像。
一般情况下,用户可以将作业提交给作业管理系统,由它负责调度当前最空闲、最有效的计算结点来执行该作业。但是,MPP也允许用户登录到某个特定的结点,或在某些特定的结点上运行作业。
各个结点间的内存模块相互独立,且不存在全局内存单元的统一硬件编址。
仅支持消息传递或者高性能Fortran并行程序设计,不支持全局共享的OpenMP并行程序设计模式。
Cluster(集群)、COW
松耦合。分布式存储,MIMD,工作站+商用互连网络,每个节点是一个完整的计算机,有自己的磁盘和操作系统。
优点
投资风险小、系统结构灵活、性能/价格比高、能充分利用分散的计算资源、可扩放性好。
问题
通信性能、并行编程环境
MPP v.s. Cluster
Cluster的每个结点都是一台完整的计算机。Cluster的每个结点上都有完整的操作系统,而MPP的每个结点上通常只有操作系统的微核。Cluster的网络和操作系统均不是定制的。
Cluster的每个结点内有本地磁盘,而MPP的结点内没有。
Cluster各结点的网络接口是连接到I/O总线上的(松耦合),而MPP各结点的网络接口是连接到存储总线上的(紧耦合)。
Constellation(群)
SMP\MPP\Cluster 比较
DSM (Distributed Shared Memory)
内存模块局部在各个结点内部,并被所有结点共享。这样,可以较好地改善对称多处理共享存储并行机的可扩展能力。
内存访问属非一致内存访问模型(NUMA)。
特点
单一的操作系统映像。同SMP。
基于cache 的数据一致性。同SMP。
低通信延迟(同SMP)与高通信带宽:专用的高性能互联网络使得结点间的延迟很小,通信带宽可以扩展。
支持消息传递、共享存储并行程序设计。同SMP。
并行机以结点为单位,每个结点包含一个或多个CPU,每个CPU 拥有自己的局部cache,并共享局部存储器和I/O设备,所有结点通过高性能互联网络相互连接。
物理上分布存储:内存模块分布在各结点中,并通过高性能互联网络相互连接,避免了SMP 访存总线的带宽瓶颈,增强了并行机的可扩展能力。
单一的内存地址空间:尽管内存模块分布在各个结点,但是,所有这些内存模块都由硬件进行统一编址,并通过互联网络连接形成了并行机的共享存储器。各个结点既可以直接访问本地局部内存单元,又可以直接访问其他结点的局部内存单元。
非一致内存访问(NUMA)模式:由于远端访问必须通过高性能互联网络,而本地访问只需直接访问局部内存模块,因此,远端访问的延迟一般是本地访问延迟的3倍以上。
可扩展性强:可扩展到数百个结点,能提供每秒数千亿次的浮点运算性能。但是,由于受cache一致性要求和互联网络性能的限制,当结点数目进一步增加时, DSM 并行机的性能也将下降。
内存分类
并行结构模型示意图
五种结构特性总结
内存体系结构
存储层次
Register:CPU/GPU里的寄存器
Cacahe(高速缓冲区)
内存访问速度 << 处理器执行速度
Cache:CPU与内存间的临时存储器
它的容量比内存小的多但是交换速度却比内存要快得多。
工作原理
当CPU要读取一个数据时,首先从缓存中查找,如果找到就立即读取并送给CPU处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中。
Cache类别(分级)
L2 cache:在结点内部的cache
L1 cache:在处理器内部的cache,更小容量
L1 cache 连接CPU 寄存器和L2 cache,负责缓存L2 cache 中的数据到寄存器中。
等级越小,越靠近CPU
为了编制发挥处理器峰值性能的高性能并行程序,需要搞清楚:是cache 的映射策略,即cache 是如何从内存中取数并存储的;还有结点内部或者结点之间内存的访问模式。
Cache使用原理
Cache以cache线(line) 为其基本组成单位,每条cache线包含L个字,每个字为8个字节。
例如L=4时,每条Cache线包含32个字节。
内存空间分割成块(block),每块大小与cache 线长度一致(L个字)。
数据在内存和cache之间的移动,以cache线(8L个字节)为基本单位:
数据从内存调入cache时,不是以该单个数据字为单位,而是以该数据所在的内存块为单位,将该块的L个字一次调入cache,存储在对应的cache线中。
如果cache 中的数据单元要求写入内存空间,则也必须以cache线为单位,即该数据单元所在cache线中的所有内容一次写入内存中对应的块中。
即Cache和内存之间一次传输的大小一定是整数块,如果是小数块就向上取整。
Cache的作用
for(int i=0; i < M; i++) {
a[i] = a[i] + 5.0 * b[i];
}
如果没有cache,则内存读访问次数为2M次。 如果有cache,则内存访问次数下降为2M/L次。
程序的数据访问具有局部性,即程序中连续使用的数据一般存储在内 存的连续位置。因此,通过cache 线的一次调入,随后的数据访问可能就落 在cache 线中,从而达到减少内存访问的次数。
一次可以调入一整个L。假设总访存次数2M=L,则可以一次全调进来。
Cache设计的关键问题
Cache的性价比
Cache线大小取合适的长度
Cache线越大,则一次载入的内存数据也越多,提高性能的潜力就越大。但是,给定 cache的容量,则cache线越大,cache 线的条数就越少,产生cache 访问冲突(大家都想同时调某一个块)的可能性就越大。cache 线一般为4–8 个。
Cache级数
一般2-3级
Cache的映射策略
按映射策略,内存块的数据能够且只能复制到被映射的cache线中,而cache线中的数据也能够且只能被映射到对应的内存块中。可分为直接映射策略,K–路组关联映射策略和全关联映射策略(理论)三种。
cache线的置换策略
对K–路组关联映射策略,当某个内存块请求被置入时,如何选择组中的某条cache 线,将其数据写回内存(如果该条cache 线的数据被修改)。
LRU (Least Recently Used) 算法:置换没引用时间最长的cache线;
FIFO (First Input First Output) 算法:置换最先置入的cache线;
LFU (Least Frequently Used) 算法:置换使用频率最低的cache线;
随机算法:随机选择一条cache 线置换。
cache 数据一致性策略
需要保证同一份数据在Cache和内存中是一样的。
Write–through 策略:cache 线中的数据一旦被修改,则立即写入内存块。它的缺点是,增加了许多不必要的内存访问。
Write–back 策略:当且仅当要求进行cache线置换时,或者有外部请求访问内存块时,将cache 线的数据写入内存(由于cache线最靠近CPU,总会得到第一手数据,所以总是Cache线覆盖内存块)。
处理器、协处理器与异构计算
处理器
传统的单核处理器对计算机系统性能提升产生瓶颈,必然导致多核架构的普及并且逐步成为主流。
多核处理器
通过集成多个核来提高性能,每个核的主频可以适当降低,这样可以减少功耗。
多核处理器中的内核采用了小型的处理器,功能相对简单,这使得多核处理器的设计和验证的周期比较短,开发风险和成本能够有效地降低,而且便于优化和重新设计。
在多核处理器中,如果一个程序采用了线程级并行编程,那么这个程序在运行时可以把并行的线程同时交付给两个核心分别处理,因而程序运行速度得到极大提高。这些程序往往可以不作任何改动就直接运行在双核电脑上。多核处理器与传统的对称多处理器系统相近,所以线程级的应用可以比较容易地从传统的单处理器或对称多处理器平台移植到多核环境中。
当在多核处理器上同时运行多个单线程程序的时候,操作系统会把多个程序的指令分别发送给多个内核,从而使得同时完成多个程序的速度大大加快。
主流计算架构差异化特点
ARM(只讨论这个)
特点
体积小、低功耗、低成本、高性能;
支持Thumb(16位)/ARM(32/64位)指令集,能很好地兼容8位/16位器件;
大量使用寄存器,指令执行速度更快;
大多数数据操作都在寄存器中完成;
寻址方式灵活简单,执行效率高;
指令长度固定。
优势
ARM的众核横向扩展空间优势明显(ARM核占物理空间更小,一个ARM核的面积仅为X86核的1/7,同样的芯片尺寸下,ARM的核数是X86的4倍以上)。
支持高能耗比
通过测试,ARM架构处理器能耗平均比x86架构处理器低20%~25%。