本文主要是介绍多核(CPU)系统中并行计算Atomic原子操作与缓存一致性(memory/cache coherency),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
单线程的程序执行过程中指令处理数据(内存读写)的过程可以认为是线性连续的,也就是前后步骤硬性依存关系,第一步执行完才会执行第二步,流程逻辑简单,但是运行时性能弱,在多核的现代系统中无法发挥算力优势,也就不能提升性能,用户体验和执行效率低下。最多,只能用并发特性,更充分的利用处理指令周期的时间片。
多核系统(这里主要指的是多CPU核心的系统)中运行的程序软件,需要发挥多核的算力,不但要并发还要并行。多线程/多进程,能真正地应用上多核算力,才能发挥其优势。
有并行,就有(内存数据处理过程的)数据竞争。有数据竞争,就有会因此出现计算/处理错误。加锁,是一类常见方案。但是加锁之后,锁住的过程就是阻塞的过程,这个阻塞过程实际上限制了并行(或并发)的计算优势。Atomic原子操作,能尽可能的减少阻塞,在一个程序软的件运行时发挥多核优势。
备注:
1. 这里所说的内存(DRAM)和缓存(SRAM Cache)是两个不同的概念,他们的背景是:都处在同一台物理机上。而缓存是个普遍存在的概念,在文件系统、Web端、服务器系统等等都广泛存在缓存这个概念,本文中的缓存指的只是CPU与内存之间的高速缓存(SRAM),简称为cpu cache。
2. 这里的所说并行不是指令级并行(Instruction-Level Parallelism)。指令级并行,指的是CPU在实现IPC(Instructions Per Cycle, 也可以叫: 单位时间内CPU的指令吞吐量)过程中,在"同一时间片"尽可能多的执行指令。例如多发射、乱序执行(Out-of-Order Execution), 动态分支预测(Dynamic Branch Prediction),数据预期(Data Prefetch),以及SSE、AVX等操作就是提升指令级并行能力。指令并行机制会影响内存读写顺序,所以要注意。PowerPC和ARM等弱排序cpu会进行指令重排(依赖内存栅栏指令)。而Intel x86, x86-64强排序cpu,总能保证按顺序执行,遵从数据内存操作依赖顺序。
3. 这里不去深究硬件多线程(Hardware Multithreading)机制。
4. 现代CPU中有好几个等级的缓存。通常L1和L2缓存都是每个CPU一个的, L1缓存有分为L1i和L1d,分别用来存储指令和数据。L2缓存是不区分指令和数据的。L3缓存多个核心共用一个,通常也不区分指令和数据。 还有一种缓存叫TLB,它主要用来缓存MMU(Memory Management Unit)使用的页表,通常我们讲缓存(cache)的时候是不算它的。缓存(cache)指的是高速缓存是SRAM,DRAM是一般的内存或者称之为主存。缓存(cache)的数据取自内存,由CPU计算处理完毕,之后会存放回到cache然后由cache再存回内存。最后才会存放到其他媒介长期保存(例如硬盘)。
5. cpu的数据读取过程: cpu(寄存器) -> 系统总线 -> IO桥 -> 内存总线 -> 内存(DRAM) -> 内存总线 -> IO桥 -> 系统总线 -> cpu(寄存器)。
6. cpu的数据存储过程: cpu(寄存器) -> 系统总线 -> IO桥 -> 内存总线 -> 内存(DRAM) 这个过程中内存从内存总线读取地址(由cpu将寄存器中的地址复制也就是写到系统总线)并等待数据到达。接着 cpu将寄存器中的数据复制到系统总线,。最后,内存(DRAM)从内存总线读取数据并写入到DRAM中。
7. 由于c++的内存模型允许编译器做调整,所以对于开发者来讲即便是单核cpu运行环境,也要注意内存顺序问题。因为重排序会导致内存读写顺序的变化。例如, 编译器被禁止牵涉到依赖链的对象上的推测性加载,这会影响既定的内存顺序管理逻辑。从这里可以看到,对于内存读、修改、写顺序的影响,除了多线程/多进程\多核心这种注意因素外,还有指令并行机制和编译器编译过程也会对这些顺序造成影响。
8. 关注内核态和用户态对于内存操作的不同影响。因为内存的同步如果涉及到内核态,一般来讲会引起昂贵的上下文切换,例如Mutex,如果这个切换占用了1000个以上的cpu instuctions cycles,那就是很昂贵的性能代价,而原子指令,则能优化这样的问题。
9. 单线程代码不需要关心指令乱序的问题。因为指令乱序至少要保证这一原则:不能改变单线程程序的执行行为。
10. 内核对象多线程编程在设计的时候都阻止了它们调用点中的乱序(已经隐式包含memory barrier),不需要考虑乱序的问题。
11. 在用户模式下的处理线程同步时,乱序的效果才会显露无疑。很多时候我们编写代码处理业务问题,实际就是用户模式下。
不可分割的Atomic原子操作,意味着这个原子操作过程对所有访问者(线程)而言,要么看到此操作之前的状态,要么看到此操作之后的状态。此过程不可分割和不可打断。
多发射与数据乱序相关的Tomasulo算法: https://zhuanlan.zhihu.com/p/499978902
指令动态调度算法: 记分牌算法和tomasulo算法
松散序列(Relaxed ordering): 当前线程自己看到的,也只表明自己看到的东西,和其他线程看到的东西没有依赖关联关系。
缓存一致性协议MESI
MESI其实就是使用四种状态来标识缓存条目当前的状态,来保证了高速缓存内数据一致性的问题。以下是四种状态说明。
(M)Modified:
表示高速缓存中相应的缓存行内的数据已经被更新了。由于MESI协议中任意时刻只能有一个处理器对同一内存地址对应的数据进行更新,也就是说再多个处理器的高速缓存中相同Tag值的缓存条目只能有一个处于Modified状态。处于此状态的缓存条目中缓存行内的数据与主内存包含的数据不一致。
(E)Exclusive:
表示高速缓存相应的缓存行内的数据副本与主内存中的数据一样。并且,该缓存行以独占的方式保留了相应主内存地址的数据副本,此时其他处理上高速缓存当前都不保留该数据的有效副本。
(S)Shared:
表示当前高速缓存相应缓存行包含相应主内存地址对应的数据副本,且与主内存中的数据是一致的。如果缓存条目状态是Shared的,那么其他处理器上如果也存在相同Tag的缓存条目,那这些缓存条目状态肯定也是Shared。
(I)Invalid:
表示该缓存行中不包含任何主内存中的有效数据副本,这个状态也是缓存条目的初始状态。
M 修改 (Modified) 这行数据有效,数据被修改了,和主内存中的数据不一致,数据只存在于本Cache中。
E 独享、互斥 (Exclusive) 这行数据有效,数据和主内存中的数据一致,数据只存在于本Cache中。
S 共享 (Shared) 这行数据有效,数据和主内存中的数据一致,数据存在于很多Cache中。
I 无效 (Invalid) 这行数据无效。
Cpu cache line是有tag和flag的,大概结构是: 4字节Tag + 56字节数据内容 + 4字节Flag(存放MESI)
CPU 内部有两个与内存/LLC(Last Level Cache)相关的组件:CA(cache agent,负责cache内容的管理)和HA(home agent,负责内存的读写操作)。
由于内存在系统视图上是一个整体,具有独立的一致性,即无论有多少物理socket,在整个系统中有且仅有一个逻辑HA。
而Cache(特指LLC,下同)仅在每个socket内有效,故逻辑上CA有多个,
每个socket的CA在逻辑上是独立的。根据上面描述,CA和HA之间仅存在如下几种通讯方式:CA 到其他CA、CA到HA、HA到CA。
L1、L2、L3 这几级Cache究竟在哪里? https://zhuanlan.zhihu.com/p/31422201
CAS Latency
什么是CAS,CL(CAS Latency)? CAS意为列地址选通脉冲(Column Address Strobe 或者Column Address Select),CAS控制着从收到命令到执行命令的间隔时间,通常为2,2.5,3这个几个时钟周期。在整个内存矩阵中,因为CAS按列地址管理物理地址,因此在稳定的基础上,这个非常重要的参数值越低越好。过程是这样的,在内存阵列中分为行和列,当命令请求到达内存后,首先被触发的是tRAS (Active to Precharge Delay),数据被请求后需预先充电,一旦tRAS被激活后,RAS才开始在一半的物理地址中寻址,行被选定后,tRCD初始化,最后才通过CAS找到精确的地址。整个过程也就是先行寻址再列寻址。从CAS开始到CAS结束就是现在讲解的CAS延迟了。因为CAS是寻址的最后一个步骤,所以在内存参数中它是最重要的。
CL(CAS Latency):为CAS的延迟时间,这是纵向地址脉冲的反应时间,也是在一定频率下衡量支持不同规范的内存的重要标志之一。
RAS(Row Address Strobe, 行地址信号)
对于CA中管理的Cache来说,分为如下的几个状态,习惯上称之为MESI或者MESIF状态机:
Modified:已被编辑状态,表示当前CA中的cache已经被修改且尚未同步到内存。
Exclusive:当前的CA已经获取了cache对应的内存写引用,但目前尚未修改。
Shared:CA仅获取了cache对应内存的读引用,无法直接修改内存。
Invalid:CA没有获取对应内存的任何引用,即系统不需要读/写对应的内存。
Memory Barrier
处理器支持哪种内存重排序(LoadLoad重排序、LoadStore重排序、StoreStore重排序、StoreLoad重排序),就会提供相对应能够禁止重排序的指令,而这些指令就被称之为内存屏障(LoadLoad屏障、LoadStore屏障、StoreStore屏障、StoreLoad屏障)
如果用X和Y来代替Load或Store,这类指令的作用就是禁止该指令左侧的任何 X 操作与该指令右侧的任何 Y 操作之间进行重排序(就是交换位置),确保指令左侧的所有 X 操作都优先于指令右侧的Y操作。
这篇文章中的后面部分相关讲解挺详细的:https://cloud.tencent.com/developer/news/706457
这篇关于多核(CPU)系统中并行计算Atomic原子操作与缓存一致性(memory/cache coherency)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!