Intel, AMD及VIA CPU的微架构(30)

11. Skylake流水线

Skylake是Haswell与Broadwell设计的进一步发展。缓存与解码器前端与Haswell基本相同,具有更大的带宽,同时执行引擎被重新组织,以提升吞吐率。

支持512位向量寄存器的Skylake版本已经对外公布,但尚未面市(2015下半年)。

目前,Skylake有2 – 4个核,大多数版本能在每个核上运行两个线程。大多数关键资源在同一个核上运行的线程间共享,如第120页所述。

Skylake使用与Broadwell相同的14 nm技术,但支持更快的DDR4 RAM。

11.1. 流水线

流水线非常类似于之前的设计,但执行单元稍作重组,几乎所有重要的指令有超过一个执行端口。它的设计目标是每时钟周期4条指令的吞吐率。

根据Intel的公开信息,用于乱序执行的资源增加了。在Skylake上,重排缓冲有224项。保留站有97项。Skylake有180个整数寄存器及168个向量寄存器。

在那些可以在每个核里运行两个线程的CPU模型中,两个线程间共享流水线的所有部分。每个线程得到总吞吐率的一半。

11.2. 指令获取与解码

在单线程应用中,指令获取单元每时钟周期最多可以获取16字节代码。

有4个解码器,以第109页对Sandy Bridge所描述的方式,每时钟周期它们可以处理4条指令(使用融合5条),生成最多4个μop。具有任意数量前缀的指令在一个时钟周期里解码。对重复的前缀,没有处罚。

长度改变前缀的惩罚与Sandy Bridge相同(参考第109页)。带有立即数,使用一个操作数大小前缀的算术与逻辑指令,如add ax, 1234,在解码器在有2 – 3个时钟周期的惩罚,不管是否对齐。这适用于32位或64位模式中,所有带有一个16位立即数常量的算术与逻辑指令。对长度改变前缀,移动指令没有惩罚。

11.3. ​​​​​​​μop缓存

μop缓存有与Sandy Bridge相同的大小与结构(参考第109页)。它被组织为32组✕8路✕6 μop,总的能力1536个μop。对每个对齐的连续32字节代码块,可以分配3行,每行6 μop。

耗尽μop缓存的代码不受获取与解码单元的限制。每时钟周期可以发布一个缓存行,但平均吞吐率不能超过每时钟周期4个μop(可能融合)或等价的32字节代码。在平均指令长度超过4字节时,μop缓存是一个大的优势。Sandy Bridge的局限与弱点仍然适用。细节参考第109页。

​​​​​​​11.4. 回路缓冲

处理器有一个从μop队列回收μop的循环缓冲,每线程64项。循环缓冲很少用完队列所有的64项,但最多30个,有时40个μop的小循环将从循环缓冲受益。循环缓冲给出每时钟周期4个μop的稳定吞吐率,与小循环的指令长度无关。扼要而言,流水线有3个不同的输入来源,依赖于关键循环的大小:

  • 循环缓冲用于最多30~40条指令的小循环。吞吐率每时钟周期4 μop,不受指令长度限制。
  • μop缓存用于最多约1000条指令的循环。吞吐率是每时钟周期最多4条指令或32字节代码。
  • 获取与解码单元用于不在μop缓存里的指令,吞吐率是每时钟周期最多4条指令或16字节。

在μop缓存与循环缓冲里,融合指令对(参见下面)算作一个。根据每时钟周期两个融合非采用分支,从循环缓冲或μop缓存获得每时钟周期6条指令的最大吞吐率是可能的。

在μop的3个来源间,分支误预测惩罚可能不同,但我不能证实这样一个差异,因为测量中的高误差。在所有3个情形里,测得的误预测惩罚在16到20个时钟周期。

11.5. ​​​​​​​微操作融合

以与之前处理器相同的方式使用μop融合。某些在执行单元中需要2个μop的指令,可以使用μop融合技术,从解码器到保留站,将这两个μop保持在一起,以节省流水线带宽。然后保留站将向2个不同的端口提交2个μop。大多数内存写指令与大多数带有内存操作数的算术及逻辑指令使用μop融合,不管寄存器的大小。进一步解释,参考第80页与95页。

解码器每时钟周期可以处理4个μop融合指令。在手册4“指令表”中可以查到使用μop融合的指令。使用μop融合的指令在“非融合域”下列出的μop数大于“融合域”下列出的μop数。

11.6. ​​​​​​​宏操作融合

以与之前处理器相同的方式,处理器可以将两条指令融合为一个μop(参考第95页)。

在特定的情形下,解码器将一条算术或逻辑指令与一条后续的条件跳转指令融合为一个计算分支μop。在执行单元处,计算分支μop不分解为2个μop,而是由端口0或6处的分支单元作为一个μop执行。

CMP,ADD与SUB指令可与有符号及无符号分支指令融合。INC与DEC可与有符号分支指令融合,TEST与AND指令可与所有的分支指令(包括无用的组合)融合,如第112页所示。

第一条指令可以有一个立即数或内存源操作数,但不能有两者。它不能有内存目标操作数。它不能有RIP相对取址的内存操作数。

JECXZ与LOOP指令不能融合。

不像之前的处理器,即使跨越16字节代码边界,也可以进行融合。

两个可融合对可在同一个时钟周期里解码。

程序员应该将任何可融合算术指令与一条后续条件跳转指令保持在一起,以利用宏操作融合,而不是在中间调度其他指令。所有4个解码器都支持宏操作融合。

11.7. ​​​​​​​栈引擎

栈引擎类似于Sandy Bridge,如第113页所述。在栈操作,如push,pop,call或return分散在显式访问栈指针的指令中时,如add rsp, 8或mov eax, [rsp+16],自动插入一个额外的栈同步μop。

11.8. ​​​​​​​寄存器分配与重命名

所有的整数、浮点、MMX、XMM、YMM、标记及还可能段寄存器可以被重命名。浮点控制字也可被重命名。

寄存器重命名由重排缓冲及调度器控制。没有观察到寄存器分配与重命名成为一个瓶颈。

无关的特殊情形

将一个寄存器置零的一个常用方式是通过与自身XOR或减去自身,比如XOR EAX, EAX。处理器知道,如果两个操作数寄存器是相同的,特定的指令与寄存器之前的值无关。在寄存器分配阶段,这个寄存器被置零,无需使用任何执行单元,也无需等待寄存器之前的值可用。

这适用于以下指令:XOR,SUB,PXOR,XORPS,XORPD及PSUBxxx与PCMPGTxx的各个版本。

带有v前缀的指令具有相同行为。不使用执行单元,吞吐率是每时钟周期4个置零操作。

这作用于所有32位与64位通用寄存器,及所有128位与256位向量寄存器。它不能用于8位及16位寄存器,因为仅部分寄存器被置零。它部分作用于64位mmx寄存器:寄存器被置零,无需等待之前的值,但它使用一个执行单元(以解决作为浮点栈寄存器与mmx寄存器的双重使用)。

如果两个寄存器相同,PCMPEQxx指令将所有比特置1。这条指令被认为与寄存器之前的值无关,但它需要一个执行单元。因为与x87形式的浮点栈寄存器重叠,带有一个64位mmx寄存器的置零指令也使用一个执行单元。

指令PCMPEQxx的各个版本可以将一个寄存器置零,无需等待该寄存器之前的值。不过,它使用一个执行单元。

以下指令没有两个输入操作数是同一个寄存器这样的特殊情形: CMP,SBB,ANDN,PANDN,ANDNPS,ANDNPD,CMPEQPS,CMPEQPD。

不需要执行单元的指令

上述由指令,如XOR EAX, EAX,将一个寄存器置零的特殊情形,由寄存器分配阶段处理,无需使用任何执行单元。

其他几个指令的处理也无需使用执行单元。这些是CLC,FXCH,NOP(包括长nop),但不包括FNOP。

移动指令的消除

以与Ivy Bridge相同的方式,大多数寄存器到寄存器移动在寄存器分配阶段消除,如第114页所述。在超过80%的可能情形下,移动消除通常会成功。串接移动也可以消除。

对所有32位及64位通用寄存器及所有128位与256位向量寄存器,移动消除是可能的。对8位与16位寄存器是不可能,对64位mmx寄存器也不可能。

Skylake不能消除零扩展移动,但可以消除隐含零扩展的移动,如MOV EAX, EBX(零扩展到RAX),及VMOVAPS XMM0, XMM1(零扩展到YMM0)。

到一个寄存器自身的移动不会被消除。例如mov eax, eax不会被消除。

一个被消除的移动有0时延,且不使用任何执行单元。但消耗解码器的带宽。

11.9. ​​​​​​​执行单元

Skylake有若干通过8个执行端口访问的执行单元。这给出了执行单元中每时钟周期8个μop的最大吞吐率。不过,整个设计的吞吐率很少超过每时钟周期4条指令。因此,即便使用μop融合,在一个临时突发以外,维持所有执行端口忙碌,是不可能的。

大多数执行单元是双倍的,因此一个μop很少需要等待一个空闲的单元。有4个整数ALU,因此大多数常见的整数操作可以每时钟周期4条指令的吞吐率执行。有3个可以处理整数向量操作的端口。2个端口可以处理浮点向量操作。2个端口可以处理分支。2个端口可以处理内存读操作,1个端口可以处理内存写。相比之前的处理器,在同一个端口上Skylake可以处理更多不同的时延。

端口

操作

时延

0

整数与向量算术、逻辑、偏移

1

0

向量字符串指令

3

0

浮点加、乘法、FMA

4

0

AES加密

4

0

整数向量乘法

5

0

整数与浮点除法、平方根

可变

0

分支

1-2

1

整数与向量算术、逻辑、偏移

1

1

整数乘法、比特扫描

3

1

浮点加法、乘法、FMA

4

1

整数向量乘法

5

2

读、地址生成

 

3

读、地址生成

 

4

 

5

整数与向量算术、逻辑

1

5

向量混排

13,如果跨通道)

5

x87浮点加法、SADBW

3

5

PCLMUL

7

6

整数算术、逻辑、偏移

1

6

跳转与分支

1-2

7

读与写地址生成

 

表11.1. Skylake里的执行单元

所有的向量执行单元都有完整的256位能力,除了除法、平方根及加密。一个256位单元不能分解,同时用于两条128位指令。

整数向量操作的时延与通用寄存器里的操作几乎相同。这使得在用完通用寄存器时,对简单的整数操作使用XMM寄存器,成为可能。

融合乘加

Skylake有两个可以处理浮点加法与乘法,以及类型为a = b * c + d的融合乘加(FMA)指令的执行单元。

FMA指令在一条指令与一个μop里完成一次乘法与一次加法或减法,所花时间与仅做一次乘法或加法相同。FMA指令可以提升经常出现乘法与加法组合的浮点代码的性能。

一个μop可以有多少输入依赖?

直到Ivy Bridge,所有有乱序执行能力的Intel处理器,都有μop不能有超过2个输入依赖的设计局限。Haswell中融合乘加(FMA)指令的引入,有必要去掉每个μop两个输入依赖的限制。因此,FMA指令是Intel处理器上第一批使用超过2个输入依赖μop的指令。随后加入了几条3个输入依赖的指令:Broadwell里的进位加、借位减以及条件移动,Skylake里的一条混合(blend)指令。

读写带宽

有两个相同的内存读端口(端口2与3)及一个写端口(端口4)。这些端口都有完整的256位宽度。这使得使用任何最多256位的寄存器,每时钟周期进行两次内存读与一次内存写成为可能。测得的吞吐率比这稍低,因为缓存的影响。所有的写操作需要在端口2,3或7上的地址计算。Skylake有72个读缓冲与56个写缓冲。

数据旁路时延

如第100页所述,执行单元被分为域,在整数域里一条指令的输出被用作浮点域中一条指令的输入时,有时有1个时钟周期的时延。例如:

; Example 11.1. Data bypass delays

addps xmm0, xmm1

por xmm0, xmm2

mulps xmm0, xmm3 ; 1 clock delay

可以通过更合适的ORPS替换POR指令,避免例子11.1中的时延。

不过,在Skylake处理器上这样的时延罕见。在以下情形里,我没有找到这样的时延:

  • 当一条浮点布尔指令,比如ORPS用于整数数据时
  • 在使用错误类型的移动指令时,比如MOVPS或MOVDQA
  • 在使用错误类型的混排指令时,比如SHUFPS或PFHUFD
  • 在使用错误类型的混合指令时,比如VPLENDD或BLENDPS

在通用寄存器与向量寄存器间移动数据的指令,比如MOVD,有2时钟周期的时延。

256位向量

所有向量执行单元有完整的256位吞吐率,除了除法、平方根及加密。

用于YMM寄存器上所有操作的256位数据通道被分为两个128位通路(lane)。所有可以在这两个通路间移动数据的指令有3时钟周期的时延,而其他移动指令仅有1时钟周期的时延。例如:

; Example 11.2. Moving data between 128-bit lanes on Haswell

vextracti128 xmm0, ymm1, 1      ; move from upper to lower lane, 3 clocks

vextracti128 xmm0, ymm1, 0      ; move in over lane only, still 3 clocks

vmovdqa xmm0, xmm1                ; same operation in 0 - 1 clocks

例子11.2中的第二条指令有3时钟周期的时延,即使所有的数据都在较低的通路中,因为VEXTRACTI128指令可能在通路间移动数据。可以通过使用在这里作用相同,但不会跨通路移动数据的VMOVDQA指令,来避免这个时延。

之前处理器中混用256位VEX代码与128位非VEX代码的严厉的惩罚(参考第112页,章节9.12),在Skylake上不再出现。这意味着一个256位寄存器的两个半部,可以被128位非VEX指令独立处理。

256位向量操作的预热时间

在不使用时,处理器关闭256位执行引擎的高半部,以节省能耗。在一个大约56,000个时钟周期或14 μs的初始预热期间,使用256位向量指令的吞吐率比普通指令慢4.5倍。在这段预热期间后,包含256位向量操作的代码序列全速运行。在最后256位指令的2.7百万个时钟周期或675 μs之后,处理器回到慢速256位执行模式(这些时间在一个4 GHz处理器上测得)。

在一个关键256位指令序列前至少56,000个时钟周期,给出一条假的(dummy)256位指令,可以让处理器为执行256位指令做好准备。在一段包含耗时256位操作代码之前的某处,可以插入一条256位向量指令,比如vxorps ymm0, ymm0, ymm0。任何使用YMM寄存器的指令将启动预热过程,除了vzeroupper与vzeroall。第一条256位指令需要150 ~ 250个时钟周期——可能启动一个加速过程。

在不使用时,处理器关闭256位单元的高128位通路,而不只是降低时钟频率。在预热期间,在执行一条256位指令时,它使用128位通路两次。在这期间,256位指令的性能主要受限于吞吐率,而不是时延。

下溢与次正规值

当浮点操作接近下溢时,次正规值出现。在某些情形里,处理次正规值代价很高,因为次正规结果由微代码异常处理。

在所有一个正规值上的操作给出一个次正规结果的情形里,处理器有一个大约129个时钟周期的惩罚。对一个正规与次正规值间的乘法或除法,有类似的惩罚,不管结果是否正规。一个正规与非正规值间的加法没有惩罚,不管结果如何。对上溢、下溢‘无穷或非值结果没有惩罚。

如果在MXCSR寄存器中同时设上“flush-to-zero”与“denormal-are-zero”模式,次正规值的惩罚可以避免。

​​​​​​​11.10. 部分寄存器访问

通用寄存器的不同部分可以保存在不同的临时寄存器中,以消除假的依赖性。在写一个寄存器部分,后跟读相同寄存器更大部分时,会出现一个问题:

; Example 11.3. Partial register access

mov al, 1

mov ebx, eax

处理器解决了这个问题,没有任何可见的性能代价。可能它进行了部分寄存器与完整寄存器的簿记。这也适用于Skylake上256位向量寄存器的两个半部。

如果修改了其中一个高8位寄存器(AH,BH,CH,DH),随后读该寄存器更大的一部分,情形就不同的:

; Example 11.4. Partial register problem with AH

mov ah, 1

mov ebx, eax

这里,插入一个额外的μop,在MOV EBX, EAX指令前,将AH与EAX的余下部分整合为一个临时寄存器。这导致了1时钟周期的额外时延。

标记寄存器的部分访问

在修改标记寄存器一部分,随后读该寄存器更大的一部分时,出现类似的情形。某些这样的情形要求像例子11.4那样的额外μop。

; Example 11.5. Partial flags access

inc eax                          ; modifies zero flag but not carry flag

jbe L1                            ; reads both sero flag and carry flag

 

; Example 11.6. Partial register access

bt eax, 2                        ; modifies carry flag but not zero flag

cmovbe eax, ebx         ; reads both carry flag and zero flag

在这些例子中,处理器将插入一个额外的μop来合并标记寄存器的两部分。

在像这样的情形里,你可能认为这是一个编程错误,或者是使用单条指令对两个不同条件进行检测的、深思熟虑的测试。

在一个偏移行旋转指令后读标记寄存器时,没有惩罚或额外的μop。

向量寄存器的部分访问

写向量寄存器部分,对该寄存器的之前值有依赖性。参考第103页,例子8.8。

在VEX指令中,YMM寄存器的两个半部视为相关,但在VEX与非VEX模式间切换时,这两部分可以独立,如章节9.12所述。不像之前的处理器,在VEX与非VEX指令间的切换,Skylake没有惩罚,但仍然对这两个状态进行区分。一开始,处理器在“高位干净状态”,其中所有YMM寄存器的高128位已知为零。在执行一条256位指令时,它将变为“高位脏状态”。在“高位脏状态”中的一条非VEX XMM指令的执行将把结果的低128位与之前寄存器值的高128位合并。这导致一个不期望的额外依赖。因此,仍然建议在一个256位指令序列后执行VZEROUPPER指令,如果后面可能跟着非VEX XMM指令。VZEROUPPER或VZEROALL指令将使CPU回到“高位干净状态”。

11.11. ​​​​​​​缓存与内存访问

缓存

Skylake

μop缓存

每核1536 μop8路,每行6 μop

1级代码

每核32 kB8路,64组,每行64 B,时延 4

1级数据

每核32 kB8路,64组,每行64 B,时延 4

2

每核256 kB4路,1024组,每行64 B,时延 14

3

共享3-8 MB16路,每行64 B,时延 34

表11.2. Skylake上的缓存大小

每个核上有一个缓存,除了3级缓存。在一个核可以运行两个线程的地方,所有的缓存都在这两个线程间共享。可能将来会有更多有不同3级缓存大小的版本。某些版本可能有4级缓存。

256位读与写(参考第127页)使得使用256位寄存器拷贝或清零大块内存很有利。如果源与目标都对齐到32,REP MOVS指令效率最高。在其他情形里,最好调用使用256位寄存器的库函数。

缓存库冲突

在之前某些处理器中,缓存库冲突现象是一个性能问题。现在这个问题被解决了。在同一个时钟周期里,总是可以进行两次缓存读,而不会导致缓存库冲突。

不过,具有相同组与偏移的内存地址间的假依赖性问题仍然存在。不可能同时从分开4K倍数字节的地址进行读写:

; Example 11.7. False memory dependence

mov [rsi], eax

mov ebx, [rsi+1000H] ; False memory dependence

理论最大吞吐率是每时钟周期两次缓存读与一次写。不过,这个吞吐率不能持续,因为有限的缓存路数,读与写缓冲等。某些内存写可以使用端口2或3进行地址计算,而不是端口7,因此推迟了读。

11.12. ​​​​​​​写转发暂停

在特定条件下,Skylake处理器可以将一个内存写转发给相同地址的一个后续读。比之前的处理器,写转发快了1时钟周期。后跟读相同地址的一个内存写,对32或64位操作数的最好情形,需要4时钟周期,其他大小操作数则需要5时钟周期。

在一个128位或256位操作数没有对齐时,写转发有最多额外3时钟周期的惩罚。

在一个任意大小的操作数跨越缓存行边界时,如可被64字节整除的地址,写转发通常需要额外4 ~ 5时钟周期。

写后跟相同地址更小的一个读时,没有或惩罚极小。

64位或更小的写,后跟一个包含在所写范围,但更小的有偏移读时,有1 ~ 3时钟周期惩罚。

128位或256位对齐写,后跟读其二分之一或四分之一的之一、之二或之四,没有或惩罚极小。不是这些二分之一或四分之一倍数的部分读需要额外的11时钟周期。

比写更大的读,读覆盖了写以外的字节,大约额外需要11时钟周期。

11.13. ​​​​​​​多线程

处理器的大多数版本可以在每个核上运行两个线程。这意味着每个线程仅得到一半资源。

与之前的处理器一样,1级缓存、指令获取、解码与执行单元在同一个核上执行的两个线程间共享。

如果任何共享资源是性能的限制因素,每个核运行两个线程没有好处。有如此多的执行端口与执行单元,执行很少会成为限制因素。如果代码力求每时钟周期超过两条指令,或者如果缓存大小是限制因素,那么在每个核上运行两个线程没有好处。在CPU中没有办法让一个线程比另一个线程优先级更高。

11.14. ​​​​​​​Skylake中的瓶颈

在Skylake中流水线与执行单元相当高效。内存访问是最常见的瓶颈。

指令获取与解码

指令获取速率仍然限制为每时钟周期16字节,对不能很好放入μop缓存的代码,这可能是一个瓶颈。

μop缓存

对最多大约1000条指令的循环,μop缓存都是有效的。

在CPU密集代码中,节约使用μop缓存是重要的。能放入μop缓存的循环与不能的循环间的性能差异会相当显著,如果平均指令长度超过4字节。μop缓存有与之前的处理器相同的弱点。

执行端口与执行单元

执行端口与执行单元有高的能力。最常见的整数指令有4个执行单元可选,大多数浮点与向量指令有2到3个执行单元可选。因此,如果代码没有长的依赖链,获得每时钟周期4条指令的吞吐率是可行的。

向量执行单元与数据通路有完整的256位宽度。这有利于使用256位向量寄存器与AVX或AVX2指令集。

收集(gather)指令能高效地将非连续数据收集到向量寄存器中,或向量化基于表的查找函数。收集指令比之前的处理器高效。

融合乘加(FMA)指令有助于提升浮点代码的性能。浮点向量加法、乘法与FMA指令,都有每时钟周期两条指令的吞吐率,以及4时钟周期的时延。

在大多数情形里,寄存器到寄存器被消除为0时延。MOVZX指令不能被消除。

执行时延与依赖链

执行时延通常是低的。大多数整数ALU操作仅有1时钟周期时延,即使对256位向量操作,而浮点算术操作有4时钟周期时延。在长依赖链中,这些执行时延是关键的。

分支预测

分支目标缓冲的大小与分支预测器的构造未知,但至少预测成功率看起来还不错。

被采用分支的吞吐率是每时钟周期一个跳转,或两时钟周期一个跳转,依赖于分支的密度。预测的不采用分支有更高的每时钟周期两个的吞吐率。因此,组织分支,使它们不被采用的可能性最大,是有好处的。

内存访问

内存端口的宽度与Haswell及Broadwell的相同。到1级缓存的理论最大吞吐率是每时钟周期两次32字节读与一次32字节写。这使得以每时钟周期32字节的速度拷贝内存块成为可能。不过,缓存的持续吞吐率总是低于理论最大值,因为缓存路数、读与写缓冲等的限制。

2级缓存的吞吐率显著低于1级缓存。在Skylake上2级缓存有4路,而Haswell与Broadwell有8路。缓存库冲突不再是一个问题。

多线程

大多数关键资源在线程间共享。这意味着在多线程应用中,瓶颈变得更为关键。

文献

Intel: "Intel 64 and IA-32 Architectures Optimization Reference Manual". January 2016.

Orestis Bastounis: "Intel’s Skylake Core i7-6700K reviewed: Modest gains from a full Tick-Tock cycle". http://arstechnica.co.uk/gadgets/2015/08/intel-skylake-core-i7-6700k-reviewed/

Intel Developer Forum 2015, session SPCS001: http://myeventagenda.com/sessions/0B9F4191-1C29-408A-8B61-65D7520025A8/7/5

你可能感兴趣的:(Agner,Fog编写的优化手册)