打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
解读 Intel? Microarchitecture
以 Intel? sandy bridge 微架构为例,了解一下 Intel 近代微架构。
上图是 Intel? sandy bridge 微架构的流水线示意图,实行了“发射-执行-完成”相分离包括下面的组件(乱序执行按序完成):
in-order front-end:程序执行顺序的前端组件,包括了: L1 ICache 与 ITLB ,指令通过 ITLB 查找 fetch(提取)进入到 32K 的 L1 指令 cache。
pre-decoder,一个预解码器,主要用来解析指令的长度,处理 LCPs(length changing prefixes)。
instruction queue,经过初步解析后存放的指令队列。
decoder,4个解码器。其中一个为复杂解码器,能解码所有 x86/x64 指令。三个简单解码器,负责解码为一个 micro-op。
decoded ICache,解码后的 uops(micro-ops)cache。
MSROM(microcode sequencer ROM),一个 microcode sequencer ROM(微代码装置 ROM),存放复杂指令的 micro-op 流。
BPU(branch prediction unit),分支预测单元。
micro-op queue,排列 decoded ICache 的 uops。
out-of-order engine,乱序发射的引擎组件,包括了: allocater/renamer,资源分配器与重命名器。Renamer 将 x86 架构性(architectural)的源/目标操作数(寄存器)重命令为微架构性(microarchitectural)源/目标操作数(寄存器),解决 uops 间的 false-dependencies(假依赖),并形成 out-of-order 的“data flow”(数据流)发送到 scheduler。 Allocater 分配 uops 需要的 load buffer 以及 store buffer。
scheduler,调度器。等待资源可用(dispatch port 可用,read buffer 或者 store buffer 可用)以及 uop 的操作数已经准备好后,将 uop 绑定到相应 dispatch port 后分派到执行单元。scheduler 每个 cycle 最多可以分派 6 个 uops 到执行 port。
in-order retirement,按序完成单元。使用 reorder buffer 保存 uops 各个阶段的结果,确保 uops 的执行结果(包括任何可能遇到的异常,中断)按原始程序的次序完成。
execution unit,执行单元,含有 6 个执行 port 以及 3 个类型的 stack。因此,schedular 每个 cycle 最多可以调度并分派 6 个 uops 到执行 port。
cache hierarchy,包括下面: L1 DCache:
DCU(data cache unit)
load buffers
store buffers
line fill buffers
L1 ICache
L2 cache
LLC(last level cahce)
1. in-order front-end
在按序前端里,包括了下面的组件:
L1 ICache 与 ITLB ,通过 ITLB 查找 fetch(提取)进入到 32K 的 L1 ICache。
legacy decode pipeline,下面的组件被划分到 legacy decode pipeline 里:
pre-decoder,一个预解码器,主要用来解析指令的长度,处理 LCPs(length changing prefixes)。
instruction queue,经过初步解析后存放的指令队列。
decoder,4个解码器。其中一个为复杂解码器,能解码所有 x86/x64 指令。三个简单解码器,负责解码为一个 micro-op。
decoded ICache,解码后的 uops(micro-ops)cache。
MSROM(microcode sequencer ROM),一个 microcode sequencer ROM(微代码装置 ROM),存放复杂指令的 micro-op 流。
BPU(branch prediction unit),分支预测单元。
micro-op queue,排列 decoded ICache 的 uops。
1.1 ICache 与 ITLB
在指令提取(instruction fetch)阶段,处理器通过 ITLB 查找并从内存的 16-byte 边界上提取指令到 ICache 里。当 ICache hit 时引发 ICache 每个 cycle 传送 16 bytes 到指令 pre-decoder 组件里。如果以平均每条指令 4 个字节来算,那么 ICache 能满足每个周期 4 个 decoder 的解码工作。可以认为,如果遇到较长指令的话(例如 10 个字节),ICache 传送的 16 bytes 是不能满足 decoder 的。
在 sandy bridge 微架构上,ICache 与 ITLB 的明细信息:
size: 32-Kbyte
ways: 8
ITLB 4K-page entries:128
ITLB large-page(2M/1G)entries:8
也就是说,ICache 共有 32K,8-ways 结构。ITLB 中维护 4K 页面映射结果的表项有 128 个,维护 2M 与 1G 页面映射结果的表项共有 8 个。当产生 ITLB miss 时,处理器将在 STLB(second TLB,或者 shared TLB)里继续查找。ITLB miss 而在 STLB hit 时,所需要 7 个 cycles。当然,如果 STLB 也产生 miss 则需要在页表结构里进行 walk,将需要更多的 cycles。
1.2 pre-decoder
pre-decoder 接收从 ICache 发送过来 16 bytes 的指令,它主要执行下面的工作:
确定指令的具体长度。
处理指令所有的 prefix。
标记指令的类型属性。(例如:属于分支指令)
pre-decoder 在每个 cycle 里,最多可以写入 6 条指令到 instruction queue 里。也就是说:在每个 cycle 里,pre-decoder 最多可以从 16-bytes 里解析出 6 条指令放入 instruction queue。要达到每 cycle 6 条指令,这说明平均每条指令不能超过 2 个字节。如果这 16 字节里包含多于 6 条指令(例如每条指令为 2 个字节),则在下一个 cycle 里 pre-decoder 会继续按每 cycle 最多解码 6 条指令进行解码。
例如,fetch line(16 字节)里含有 7 条指令,那么首 6 条指令会在一个 cycle 完成解码写入 instruciton queue 里,第 7 条指令将在下一个 cycle 里解码。在下一个 cycle 里,ICache 会继续发送 16 bytes 到 pre-pecoder 里,pre-pecoder 继续最多解码 6 条指令。
指令的 operand size 以及 address size 会影响着指令长度。我们知道 CS.D 决定了指令的 default operation size(默认的操作宽度)。当 CS.D = 1 时,默认的 operand size 与 address size 为 32 位。CS.D = 0 时,默认的 operand size 与 address size 为 16 位。但是,default operand-size override prefix 与 default address-size override prefix 可以改变操作数与地址的宽度,从而改变了指令固定的长度,这两个 prefix 被称为LCP(length changing prefix)。
default operand-size override prefix(66H):重新改写默认的 32 位或者 16 位 operand size 为 16 位或者 32 位。
例如:mov eax, 11223344h。它的指令编码为 B8 44 33 22 11(指令长度为 5),当插入 66H 字节时,指令编码为 66 B8 44 33(指令长度为 4)。 default address-size override prefix(67H):重新改写默认的 32 位或者 16 位 address size 为 16 位或者 32 位。
例如:mov eax, [11223344h],它的指令编码为 A1 44 33 22 11(指令长度为 5),当插入 67H 字节时,指令编码为 67 A1 44 33(指令长度为 4)
当然,也可能存在超过 1 个 LCP 的情况(同时存在 66H 与 67H)。因此,如果指令含有 LCP 的话,pre-decoder 需要确定最终的指令长度,在解码 LCP 时需要额外花费 3 个 cycles (注:在前一代架构中,解码 LCP 需要花费 6 个 cycles)。另外,REX prefix 虽然也能改变指令的长度(MOV reg, [disp32] 或者 MOV reg64, imme64),但 pre-decoder 解码时并不会花费额外的 cycles。
1.3 instruction queue
instruction queue 组件在 pre-decoder 与 decoder 之间,经过 pre-decoder 后指令的长度已经确认,从 ICache 传送过来的 16-bytes 被解析为 x86 指令写入 instruction queue(指令队列)里存放着,instruction queue 最多可以容纳 18 条指令。由于 macro-fused(宏融合,两条指令解码为 1 个 uop)的存在,instruction queue 每个 cycle 最多传送 5 条指令(其中包括了两条可以宏融合的指令)到 decoder 进行解码。
1.4 decoder
decoder 负责将 x86/x64 的 CISC 指令解码为单一功能的 micro-ops(uops,微操作),从 core 微架构开始就拥有 4 个 decoder(解码器)。第 1 个 decoder(decoder 0) 是复杂解码器,能解码所有的 x86/x64 指令,每个 cycle 最多可以解码为 4 个 uops。其余 3 个为简单解码器,将简单指令解码为 1 个 uop,每个 cycle 只能解码 1 个 uop。
第 1 个复杂解码器也能解码简单指令,因此 4 个 decoder 都能解码为 1 个 uop,包括 micro-fused(微融合),stack pointer tracking(栈指针跟踪)以及 macro-fused(宏融合)。但是,只有一个 decoder 能解码为 4 个 uops。那么,4 个 decoder 每个 cycles 最多可以解码为 7 个 uops(4 + 1 + 1 + 1)。decoder 解码后的 uops 被送入到 decoded ICache 以及 micro-op queue。
MSROM 组件负责提供复杂的 uops 数据流,在 sandy bridge 微架构里 MSROM 每个 cycle 能提供 4 个 uops,MSROM 用来帮助 decoder 解码超过 4 个 uops 的指令。因此,当指令超过 4 个 uops 将从 MSROM 里取得。向 MSROM 取 uops 的操作可以由 decoder 或者 decoded ICache 组件发起。
通过 macro-fusion(宏融合)技术,decoder 能将两条 x86 指令解码为 1 个 uop。4 个 decoder 都可以产生 macro-fused 动作,但在每个 cycle 里 4 个 decoder 只能产生一个 macro-fused。因此,在每个 cycle 里 decoder 最多能解码 5 条 x86 指令(2 + 1 + 1 + 1),最多能产生 7 个 uops。
1.4.1 micro-fusion(微融合)
x86/x64 指令允许使用“memory-to-register”类操作数,当指令的操作数是 memory 与 register 时将解码为多个 uops。例如:“ADD RAX, [RBX]”指令是一条典型的操作数为 memory 与 register 的指令,它会被解码为两个 uops:一个为 load uop,另一个为 add uop。
考查下面的一条 store 操作指令:
mov [rbx + rcx * 8 + 0Ch], rax
---------------------  ---
|               |
|               +--------> store data 操作(使用 port 4)
|
+------------------------> store address 操作(使用 port 2 或 port 3)
decoder 0 生成两个 uops:一个是 store address 操作 uop,可以通过 port 2 或者 3 执行。一个是 store data 操作 uop,通过 port 4 执行。但在 dispatch 到 execution unit(执行单元)时这两个单一的 uop 被融合为一个复杂的 uop。
micro-fused(微融合)允许将多个 uops 融合为 1 个复杂的 uop 进行 dispatch 到 execution unit(执行单元)。在发射到执行单元时,这个复杂的 uop 与单一的 uop 花费同样的 cycles。因此,使用 micro-fused 将提高从 decoder 发射到 execution unit 的吞吐量。但是,在执行单元中仍然是执行两个 uops 操作。
从 core 微架构开始引入了 micro-fusion 功能,可以将下面几类操作进行 micro-fused:
store 操作:包括了 sotre 寄存器与立即数。例如:“mov [rdi], rax”指令与“mov DWORD [rdi], 1”
load-and-operation 操作:指令的操作数是 register 与 memory,执行的是“load + op”操作(被解码为 load uop 与另一个操作 uop)。
例如:“add rax, [rsi]”,“addps mmx0,[rsi]”,“xor rax, [rsi]”指令等等...
load-and-jump 操作:这是一条分支指令,目标地址从 memory 地址 load 而来。例如:“jmp QWORD [rax]”,“call QWORD [rax]”指令。RET 指令也是属于 micro-fusion 指令,因为它从 RSP 指向的栈里 load 目标地址(即返回地址)。
memory 与 immediate 之间的 cmp-or-test 操作:CMP 或者 TEST 指令的操作数是 memory 与 immediate。例如:“cmp DWORD [rsi], 1”,“test DWORD [rsi], 1”指令等。
在 64-bit 模式下,指令使用 RIP-relative 寻址的 memory 时,在下面的情形下不能产生 micro-fused:
指令的另一个操作数是 immediate。例如:“mov DWORD [rip + 50h], 400”,“cmp QWORD [rip + 50h], 1”指令等。
RIP-relative 寻址出现在分支指令里。例如:“ jmp QWORD [rip + 50h]”指令。
1.4.2 macro-fusion(宏融合)
macro-fused 将两条 x86 指令融合为一个 uop,允许进行宏融合的两条指令需要满足下面条件:
第一条指令修改了 eflags/rflags 寄存器(不同微架构所支持的指令也不同)。
core, nehalem 微架构上只支持 CMP 与 TEST 指令。
sandy bridge 微架构上支持 CMP, TEST, ADD, SUB, AND, INC 以及 DEC 指令。
如果存在两个操作数(operand 1 与 operand 2),这些指令能产生 macro-fused 还需要满足的条件是:operand 1(目标操作数)是 register,并且 operand 2(源操作数)是 immediate,register,或者非 RIP-relative 寻址的 memory。或者 operand 1 是 memory,而 operand 2 是 register。 REG-REG:例如 cmp eax, ecx 指令。
REG-IMM:例如 cmp eax, 1 指令。
REG-MEM:例如 cmp eax, [esi] 指令(非 RIP-relative 寻址)。
MEM-REG:例如 cmp [esi], eax 指令。
但是,MEM-IMM 操作数不能产生 macro-fused,例如 cmp DWORD [eax], 1 指令。 后面的指令是条件分支指令(Jcc 指令)。但是,不同的指令,以及不同的微架构所支持的条件不同。
TEST 指令所有的条件(所有的 eflags 标志位),包括:OF,CF, ZF, SF 以及 PF 标志位。
CMP 指令根据不同微架构支持不同的条件。
core 微架构仅支持 CF 与 ZF 标志位。因此,支持下面的 Jcc 指令:
JC/JB/JNAE:CF = 1
JNC/JNB/JAE:CF = 0
JZ/JE:ZF = 1
JNZ/JNE:ZF = 0
JBE/JNA:CF = 1 or ZF = 1
JA/JNBE:CF = 0 and ZF = 0
nehalem 微架构增加了对 SF <> OF 与 SF == OF 条件的支持:
JL/JNGE:SF <> OF
JNL/JGE:SF = OF
JLE/JNG:SF <> OF or ZF = 1
JNLE/JG:SF = OF and ZF = 0
sandy bridge 微架构增加了对 ADD, SUB, AND, INC 以及 DEC 指令的支持,它支持的条件如下表所示。
条件
分支指令
TEST
AND
CMP
AND
SUB
INC
DEC
OF = 1
JO
Y
Y
N
N
N
N
N
OF = 0
JNO
CF = 1
JC/JB/JNAE
Y
Y
Y
Y
Y
N
N
CF = 0
JNC/JNB/JAE
ZF = 1
JZ/JE
Y
Y
Y
Y
Y
Y
Y
ZF = 0
JNZ/JNE
CF = 1 or ZF = 1
JBE/JNA
Y
Y
Y
Y
Y
N
N
CF = 0 and ZF = 0
JNBE/JA
SF = 1
JS
Y
Y
N
N
N
N
N
SF = 0
JNS
PF = 1
JP/JPE
PF = 0
JNP/JPO
SF <> OF
JL/JNGE
Y
Y
Y
Y
Y
Y
Y
SF = OF
JGE/JNL
SF <> OF or ZF = 1
JLE/JNG
SF = OF and ZF = 0
JG/JNLE
上表中,Y 表示支持宏融合,N 表示不支持宏融合。
在 core 微架构里,不支持 signed 数的比较产生宏融合(即 JL/JNGE, JGE/JNL, JLE/JNG 以及 JG/JNLE)。这个情况在 nehalem 微架构里得到改善,支持 signed 数的比较结果产生宏融合。
1.4.3 stack pointer tracker
PUSH, POP, CALL, LEAVE 以及 RET 指令会隐式地更新 stack pointer 值,在 core 微架构之后 decoder 负责维护这个隐式的更新 stack pointer 操作。
思考一下这条指令 “push rax”,它在以前的微架构中会产生多个 uops,大概处理如下面所示:
(1) TEMP = RAX                             ;; ===> renaming ?
(2) RSP = RSP - 8                          ;; ===> 生成 ALU uop
(3) [RSP] = TEMP                           ;; ===> 生成 STA(store address)uop 与 STD(store data)uop
那么,根据上面的拆分,decoder 大致可以解码为 3 个 uops:1 个 SUB uop,1 个 STA(store address) uop 以及 1 个 STD(store data) uop。
再来看看这两条指令“pop rax”与“ret”,大概处理如下面所示:
pop rax :
(1) rax = [RSP]                           ;; ===> 生成 LD uop
(2) RSP = RSP + 8                         ;; ===> 生成 ADD uop
ret :
(1) RIP = [RSP]                           ;; ===> 生成 JMP uop
(2) RSP = RSP + 8                         ;; ===> 生成 ADD uop
pop rax 指令可以解码为 2 个 uop:1 个 LD(load data) uop 与 1 个 ADD uop。ret 指令可以解码为 2 个 uop:1 个 JMP uop 与 1 个 ADD uop。
引进 stack pointer tracker (栈指针跟踪器)这个功能后,将隐式栈指针更新操作移到 decoder 里实现,从而释放了 execution unit(执行单元)资源,增加了发射与执行带宽。PUSH 指令需要 2 个 uops,而 POP 与 RET 只需要 1 个 uop。
1.5 decoded ICache
由于 x86 指令的不定长以及指令解码 uops 数量的不同,需要使用 decoded ICache 来缓存从 decoder 里解码出来的 uops。送入 decoder 进行解码的 16 bytes 可能会解析出少于 4 条 x86 指条或者多于 4 条(按平均每条指令 4 个 bytes 来算),解码出来的 uops 数量也会不同,而 out-of-order 执行单元每个 cycle 最多允许执行 6 个 uops。造成前端的解码与后端执行的 uops 不匹配,引入 decoded ICache 能很大程度地缓解这些不匹配而带来的 bandwidth(带宽)瓶颈。
decoded ICache 是 8-ways 32-sets 结构,如下图所示:
每 set 的每个 way 最多能容纳 6 个 uops。因此,理想状态下整个 decoded ICache 能缓存 6 * 8 * 32 = 1536 个 uops。
每个 way 装载的 uops 是由 x86 指令字节里的 32 bytes 边界解码出来的(x86 指令 32 字节边界对齐),也就是以 32 bytes 为一个块,作为装载单位。
如果 32 bytes 指令块解码出来不足 6 个 uops 时,则 way 不会被填满而留下空位。下一个 32 bytes 指令块解码的 uop 会装入下一个 way 里。
如果 32 bytes 指令块解码出来的 uops 超过 6 个时,表明一个 way 不能装下全部 uops,则余下的 uops 会装载到下一个 way 里。最多有 3 个连续的 ways 来容纳这些 uops。也就是 32 bytes 的指令块解码出来的 uops 最多只能装入 3 个 ways 里。那么,允许一个 32 bytes 指令块最多只有 18(6 * 3) 个 uops 可以装入 decoded ICache 中。
除了上面,way 的装填还有一些限制:
如果一条 x86 指令解码为多个 uops 时,这些 uops 不能跨 way 装载,只能放入同一个 way 里。
每个 way 里最多只能装入 2 个分支 uops
当指令从 MSROM 里获得 uops 时,这些 uops 必须独占一个 way。
非条件分支 uop 必须是 way 里的最后一个 uop。
如果指令含有 64 位的立即数,这个立即数必须占用两个 way。
当由于这些限制而造成 uops 不能装入 decoded ICache 时(例如 32 bytes 指令块解码出来可能超过 18 个 uops),这些 uops 被直接发送到 out-of-order engine。
decoded ICache 是 L1-ICache(instruction cache)和 ITLB 对应的一份 shadow cache,ICache 存放的是 x86 指令字节码,而 decoded ICache 存放的是 uops。也就是说:decoded ICache 里缓存的任何一个 uops 都在 ICache 里存在着对应的 x86 指令。那么,刷新 ICache lines 的指令时,也必须刷新 decoded ICache 里指令对应 uops。
当 ITLB 的某个 entry(或全部)被刷新时,可能造成整个 ICache 被刷新,同时也会使得整个 decoded ICache 被刷新。例如:更新 CR3 寄存器值从而更新了整个页转换表结构(或者 CR4 寄存器某些页机制相关的控制位被更新)。
1.6 BPU (branch prediction unit)
流水线前端利用 BPU(分支预测单元)尽可能地在确定分支指令的执行路径之前就预测出分支的目标地址。BPU 能预测下面的分支类型:
conditional branches(条件分支):也就是 Jcc 指令族。
test eax, eax
jz @taken
... ...                                 ;; 分支跳转不成立
@taken:
... ...                                 ;; 分支跳转成立
BPU 预测这个分支跳转的目标地址。也就是预测这个跳转是否成立。
direct calls/jumps(直接的调用与跳转),它们的目标地址是基于 RIP 与 offset 值而来。如下面代码所示:
jmp @target                             ;; 直接跳转
... ...
@target:
... ...
call @fun                               ;; 直接调用
indirect calls/jumps(间接的调用与跳转),它们的目标地址从 register 或 memory 里读取。如下面代码所示:
@fun:           __func
... ...
@target:        __target
jmp DWORD [@target]                     ;; 间接跳转
;;
;; 或者:
;;      mov eax, __target
;;      jmp eax
... ...
__target:
... ...
call DWORD [@fun]                       ;; 间接跳转
;;
;; 或者:
;;      mov eax, __func
;;      call eax
returns(调用返回),也就是 RET 或 RET n 指令。使用 16 个 entries 的 RSB(return stack buffer)结构实现。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
程序的机器级表示
I-Cache与D-Cache
汇编指令
偷懒的BTB ?ARM Cortex X1 初探
dd
UBOOT添加命令的执行流程(转载)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服