Loading

Loading

GPU systems 学习笔记(一)缓存与CPU执行机制

《深入理解计算机系统》选读

笔者在学习计算机系统时参考了CMU15-213的课程、以及CSAPP的重点导读。

高速缓存

instruction cache缓存的是指令;指令访问的局部性有助于提高命中率,但只有所需指令块已在缓存中且有效、标签匹配时才命中(缓存通常在芯片内)。取出的指令接着被解码和执行。

取指时先使用指令地址查找缓存;如果没有命中,就向下一层缓存请求对应指令块。如果各级缓存都未命中且对应页面驻留在主内存中,就需要访问DRAM。

image

高速缓存的内部怎么组织

设:

  • S:一共有多少组(竖着有多少条浅紫色横带)
  • E:每组有多少行(每条横带里有多少个黑框)
  • B:每行的数据块有多少字节(每个黑框能装的数据量,图中没有展开)

image

更具体的,在每个小黑框内是这么存储的:

\[valid bit+tag+data block \]

其中,data block是缓存的数据块;用于查找缓存的地址(address of word)如下图分为tag、set index和block offset,并不是缓存行中的数据字段:

image

采用写回策略时的缓存行形态:

[ v:是否有效 ][ d:是否有未写回修改 ][ tag:标识哪个内存块 ][ B 字节数据 ]

image

如果目标组中没有有效位 \(v=1\) 且tag匹配的缓存行,就会发生缺失。在直接映射缓存中,每组只有一行;装入新块时使用这一行,若原行有效则需替换,若它采用写回策略且为脏行,还需先写回。

缓存的写入操作

如果你需要写入的数据块在cache中,我们就wirte hit了(写命中)。

这个时候有两种更新策略:

  1. write through:直接写。

    可以理解为:

    修改前:Cache:10    内存:10
    修改后:Cache:20    内存:20
    

    每次写都产生向下一层的写入流量

  2. write back:写回

    可以理解为:

    修改前:Cache:10    内存:10
    修改后:Cache:20    内存:10    ← 最新数据暂时只在 Cache
    写回后:Cache:20    内存:20
    

    连续多次修改可以合并,减少写入流量

写回策略需要记住“哪些缓存行被修改过”,所以有了图中圈出的 dirty bit(脏位)。

例如,一个缓存行先后被修改了 100 次,可以一直保留在 Cache 中,等它需要被替换时再写回。如果没有修改过,就可以直接丢弃,因为下一层已有对应数据。

那么对于没有在cache中保存的数据块的写入该如何实现呢(write miss)?

这个时候我们就要决定,要不要为这次写入把数据放入cache了。

有两种策略:

  1. write-allocate:写分配,先把对应数据块加载到 Cache,再修改 Cache 中的数据
  2. no-write-allocate:非写分配,直接把写入交给下一层,不把这个块加载到当前 Cache

image

处理器体系架构

指令集体系结构是软件与处理器之间的接口规范,包括支持的指令、指令编码、寄存器以及相关的执行行为等。

指令集体系结构常按设计风格区分为RISC(精简指令集,推荐学习)和 CISC(复杂指令集)。现代RISC处理器也可以采用复杂的微架构,许多现代CISC处理器会将指令转换为内部微操作;微操作不等于一套RISC指令集,指令集风格与内部实现需要区分。

下图是MIT讲解intel 奔腾pro的架构图。

image

约2005年前后,功耗和散热约束以及登纳德缩放失效限制了处理器频率的持续提升,推动了主流处理器向多核发展。多核此前已经存在,这一转向不能简单归因于摩尔定律失效。

人们问及“多核处理器是否是昙花一现,未来是否有发展空间?”

intel工程师:“答案是肯定的。”

为了缓解CPU处理速度与主内存访问速度之间的差距,处理器采用缓存和多级存储层次;单纯增大主内存容量并不能解决访问延迟问题。

课上用1.13倍性能、1.73倍功耗说明提高频率可能带来的代价,这些数字是特定假设或配置下的示例,不是超频的固定比例;实际结果取决于电压、频率和工作负载。(其实有的时候这是一个提升性能的好选择🤔,不过总耗电量还取决于运行时间)

课上的双核示例在特定频率、功耗预算和可并行工作负载下达到约1.73倍性能;增加核心并不保证功耗不变或获得这一加速比,实际收益受串行部分、通信和资源竞争限制。

这也是创造多核处理器的动机,在时钟频率不那么高的情况下产生更高的性能。

image

Arm的 big.LITTLE 架构将高性能大核与高能效小核组合,以适应不同负载的性能和功耗需求;不能据此认为增加小核一般都无法提升性能,收益取决于工作负载的并行性。

可以看这个bigLITTLE的介绍:https://www.bilibili.com/video/BV1xv41117tN/

在支持异构多核调度的实现中,调度器根据任务负载、延迟要求、核心容量和能耗等因素选择大核或小核,任务也可以迁移。这有助于平衡性能和能耗,但不保证始终达到最高效率。

RISC-V的基础整数指令集较小,但完整实现还可能包含乘除法、原子操作、浮点、向量等扩展,不能把基础指令数量当作整个RISC-V指令集的数量:

image

image

image

image

处理器的设计步骤

见数电。

流水线技术

流水线将指令处理划分为多个阶段,使不同指令能够同时处于不同阶段,重叠执行。

使用流水线寄存器暂存各阶段之间传递的中间结果和控制信息。

在理想的单发射流水线中,每个阶段每周期处理一条指令,不同阶段同时处理不同指令;流水线填满后可每周期完成一条指令。划分阶段可缩短组合逻辑路径、提高时钟频率和吞吐率,但不保证单条指令的延迟降低。

image

但流水线的设计也不是越深越好,随着深度的加深,分支预测失误带来的性能损失会越大,因此需要考虑工程上的折中。

流水线冒险(harzard)

使指令无法按预定时钟周期在流水线中继续推进的情况。

结构冒险

所需硬件资源正在被之前的指令工作。

解决方法:

增加硬件资源

对于端口冲突的情况,可以增加寄存器的端口数量

image

指令与数据cache分开存储

image

数据冒险

指令间的数据依赖可能导致冒险。例如,后一条指令需要的结果尚未由前一条产生;有转发时不一定要等到结果写回寄存器。更一般地还包括读后写(WAR)和写后写(WAW)冲突。

提前转发forwarding

无法解决所有的数据依赖问题。

image

控制冒险

需要根据之前指令的结果决定下一步的行为,

分支预测

硬件动态预测包括分支方向预测和目标地址预测;branch target buffer(BTB)主要缓存分支目标地址。编译器可以提供静态预测信息,或通过if-conversion等转换减少分支,减少分支本身不等于分支预测。

乱序执行 out of order execution(提升ILP)

ILP:指令级并行,让多条指令的执行发生重叠,提高速度。

在典型乱序处理器中,指令按程序顺序取指、重命名和派发;输入就绪且满足执行资源与访存顺序等约束的操作,可以被调度器选中提前执行。

最后再按程序顺序提交结果。(依赖满足、资源可用且满足相关顺序约束的操作,才有资格被调度器选中执行,并非就绪后必然立即执行)

(思考)感觉可以建有向无环图来实现拓扑排序??

好像想的过于简单了,实际上CPU在动态跑的过程中有很多依赖,有些依赖是真的,有些只是寄存器重名造成的,有些要等地址算出来才能确定,甚至后面的指令是不是该执行,都还不知道。

GPT说我要这么理解,挺清晰的

  1. 能提前算的就提前算:输入准备好了、执行资源有空,就可以执行。

  2. 不确定的可以先猜着算:例如预测分支;猜错了,撤销相关工作。

  3. 最终按顺序确认结果:保证程序表现正确。

image

现代处理器架构

指令并行

intel core i7

i7的组成是这样的

![image-20261019173636433]image

我们来看看具体的架构

image

ARM Cortex-A57

指令cache(L1 instruction cache)-> 取指(instruction fetch)-> 指令译码(decode)-> 寄存器重命名(register rename)-> 派发与发射(dispatch / issue)-> 对应执行单元乱序执行 -> 结果写回(writeback)-> 按序提交(commit / retirement)。register rename本身不是乱序执行;图右侧的in-order与out-of-order标注描述同一处理流程的不同阶段,不是两条可任选的执行路线。
image

数据并行 DLP

GPU是典型。

可以看CUDA guide来学习GPU上的并行编程。

posted @ 2026-10-20 11:35  Miya555  阅读(5)  评论(0)    收藏  举报