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

9.11. 寄存器的部分访问

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

; Example 9.4. Partial register problem

mov al, 1

mov ebx, eax

通过插入一个额外的μop来合并寄存器的不同部分,在Sandy Bridge中解决了这个问题。我假定这个额外的μop在ROB-读阶段生成。在上面的例子中,ROB-读将生成一个额外的μop,在MOV EBX, EAX指令前,将AL与EAX余下部分合并为一个临时寄存器。写一个部分寄存器没有惩罚,除非稍后读同一个寄存器更大的部分。

Ivy Bridge仅在高8位寄存器(AH,BH,CH,DH)被修改时插入一个额外的μop,在像例子9.4的情形里不会。

标记的部分访问暂停

Sandy Bridge与Ivy Bridge不仅对通用寄存器,还对标记寄存器使用一个额外μop来整合部分寄存器的方法,不像之前的处理器仅对通用寄存器使用这个方法。这发生在写标记寄存器一部分,后跟读该标记寄存器更大一部分时。因此,之前处理器的部分标记寄存器暂停(参考第89页)被一个额外的μop所替代。在一条旋转指令后读标记寄存器时,Sandy Bridge也产生一个额外的μop,Ivy Bridge不会。

向量寄存器的部分访问

在重排缓冲中,一个XMM寄存器不会分解为部分。因此,无需额外μop,在写一个XMM寄存器的部分时,也没有部分访问暂停。但写一个向量寄存器部分有对寄存器之前值的一个依赖。参考第103页的例子8.8。

在VEX指令里,YMM寄存器的两个半部总是视为相关的,但在VEX与非VEX模式间切换时,两个半部可以分开,如下所述。

9.12. VEX与非VEX模式间的转换

AVX指令集定义了三个处理器模式,如手册2“优化汇编例程”,章节13.6“使用AVX指令集与YMM寄存器”描述的那样。这三个状态是:

  1. (干净状态)YMM寄存器高半部没有使用,且已在为零。
  2. (修改状态)至少一个YMM寄存器的高半部被使用,且包含数据。
  3. (保存状态)所有YMM寄存器被分为两部分。低半部由XMM指令使用,高半部不变。所有高半部都保存在一个暂存器(scratchpad)中。如果转换到状态B需要,每个寄存器的两部分将被再次接合起来。

状态C是一个不期望的状态。在混合使用YMM寄存器的代码与使用XMM寄存器的非VEX指令代码时出现。BàC,CàB及CàA的转换,根据我的测量,在Sandy Bridge上,每个大约需要70个时钟周期。AàC及BàA的转换需要零或一个时钟周期。最好不混用VEX与非VEX代码,以及在任何使用VEX编码指令的代码序列后插入一条VZEROUPPER指令,避免至/自状态C的慢速转换。

9.13. 缓存与内存访问

缓存

Sandy BridgeIvy Bridge

μop缓存

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

1级代码

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

1级数据

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

2

每核256 kB8路,每行64 B,时延11

3

最多16 MB12路,每行64 B,时延28,共享

表9.5. Sandy Bridge上的缓存大小

每个核上有一个缓存,除了最后一级缓存。在一个核可以运行两个线程时,所有的缓存在线程间共享。将来可能有更多版本具有不同的最后一级缓存大小。

缓存库冲突

在数据缓存中,每个连续的128字节,或两个缓存行,被分为8个库,每个16字节。如果两个内存地址有相同的库号码,即两个地址的4 – 6比特位相同,不可能在同一时钟周期里执行这两个读。例如:

; Example 9.5. Sandy bridge cache bank conflict

mov eax, [rsi]                  ; Use bank 0, assuming rsi is divisible by 40H

mov ebx, [rsi+100H]      ; Use bank 0. Cache bank conflict

mov ecx, [rsi+110H]       ; Use bank 1. No cache bank conflict

另外,在具有相同组与偏移的内存地址间,即相距4K字节的倍数,存在一个伪依赖:

; Example 9.6. Sandy bridge false memory dependence

mov [rsi], eax

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

未对齐的内存访问

对操作数大小是64位或更小的非对齐内存访问,几乎没有惩罚,除非使用多个缓存库。

指令预取

Ivy Bridge预取指令有一个问题。看起来Ivy Bridge浪费时间在预取已经在缓存中的数据。在Ivy Bridge上,从同一地址重复预取的测量吞吐率是每43时钟周期一次预取,而在Sandy Bridge上是每时钟周期两条预取指令的吞吐率。

9.14. ​​​​​​​写转发暂停

在特定条件下,处理器可以将一个内存写转发给相同地址的一个后续读。相比之前的处理器,这个写转发可以在更多的情形下工作,包括非对齐的情形。写转发在以下情形工作:

  • 一个64位或更小的写,后跟一个相同大小与相同地址的读时,不管是否对齐。
  • 一个128256位写,后跟一个相同大小与相同地址的读时,16字节边界对齐。
  • 一个64位或更小的写,后跟一个更小的完全包含在写地址范围里时,不管是否对齐。
  • 一个任意大小的对齐写,后跟两部分的两个读,或四部分的四个读等,在写地址范围里自然对齐时。
  • 一个128位或256位对齐写,后跟一个64位或更小的、没有跨过8字节边界的读时。

64位或更小的写转发,跨越缓存行边界没有惩罚。如果读的操作数比之前的写要大,或者与写地址部分重叠,写转发不能工作:

; Example 9.7. Failed store forwarding when read bigger than write

mov dword ptr [esi], eax               ; Write lower 4 bytes

mov dword ptr [esi+4], edx          ; Write upper 4 bytes

movq xmm0, qword ptr [esi]        ; Read 8 bytes. Stall

在大多数情形里,对失败写转发的惩罚是大约12个时钟周期。

在写没有对齐到至少16字节边界时,对128位或256位写转发,惩罚会异常地大。在这个情形里,我测得在Ivy Bridge上,16字节读/写大约是50时钟周期,32字节读/写大约是210时钟周期。

9.15. 多线程

Sandy BridgeIvy Bridge的某些版本在每个核上可以运行两个线程。这意味着每个线程仅得到一半资源。资源以下面的方式在运行在同一个核上的两个线程间共享:

缓存:所有的缓存资源完全在线程间共享。一个线程用得越多,另一个可以用的越少。

分支目标缓冲与分支历史模式表:这些在线程间完全共享。

指令获取与解码:指令获取器与解码器在两个线程间平均共享,每个线程隔一个时钟周期使用它们。

循环缓冲:每个线程一个循环缓冲。

寄存器分配与重命名资源平均共享,每个线程隔一个时钟周期使用它们。

重排缓冲与保留站。这些完全共享。

执行端口与执行单元。这些完全共享。一个线程可以使用一个执行端口,而另一个线程使用另一个端口。

读写缓冲:这些完全共享。

永久寄存器文件:每线程一个。

显然,如果任一共享资源是性能的限制因素,每核运行两个线程没有好处。不过,在许多情形里,执行资源超出单个线程所需。在大量时间花在缓存不命中与分支误预测的情形里,每个核运行两个线程特别有优势。不过,任一共享资源是瓶颈时,每核运行两个线程没有优势。相反,每个线程很可能运行得比单个线程一半还慢,因为缓存与分支目标缓冲中的逐出,以及其他资源冲突。在CPU中没有办法给一个线程比另一个线程更高的优先级。

    1. Sandy Bridge与Ivy Bridge中的瓶颈

指令获取与预解码

指令获取与解码非常类似于之前的处理器,仍然是一个瓶颈。幸运的是,新的μop缓存降低了解码器的压力。

μop缓存

新的μop缓存有了显著的提高,因为在代码的关键部分适合μop缓存时,它消除了指令获取与解码的瓶颈。现在,很容易得到每时钟周期4条指令的最大吞吐率(或者宏融合的5条),即使对更长的指令。

程序员应该小心在CPU密集代码中经济地使用μop缓存。适合μop缓存的循环与不适合μop缓存的循环之间的性能差异相当可观,如果平均指令长度超过4字节。

在旧的P4/NetBurst处理器中,μop缓存有点类似追踪缓存(参考第39页),具有某些相同的弱点。地址与数据操作数需要超过32位储存的指令可能在μop缓存中使用额外的空间,且需要额外一个时钟周期载入。优化代码避免这些弱点是可能的,但这不可能由任何人来完成,而是最专业的汇编程序员。

对我而言,为什么处理器没有在代码缓存中标记指令边界是一个谜。这将消除指令长度解码的瓶颈,因而消除μop缓存的需要。AMD处理器这样做,旧的Pentium MMX也同样。

寄存器读暂停

这个旧的瓶颈,自Pentium Pro起困扰Intel处理器,最终被消除了。在我的测量中没有检测到这样的暂停。

执行端口与执行单元

执行端口与执行单元的能力相当高。许多μop可在2个或3个执行端口间选择,每个单元每时钟周期可处理一个完整的256位向量操作。因此,如果指令平均分配在端口之间,执行端口的吞吐率不是一个严重的瓶颈。

如果代码产生许多去往相同执行端口的μop,执行端口会是平均。

执行时延与依赖链

执行时延通常是低的。大多数整数ALU操作仅有一个时钟周期的时延,即使对256位向量操作。执行时延仅在长的依赖链中是关键的。

部分寄存器访问

在写寄存器一部分后,读整个寄存器有一个惩罚。使用MOVZXMOVSX8位或16位内存操作数读入一个32位寄存器,而不是使用寄存器更小的部分。

回收

在我的所有测试中,都没有观察到μop的回收成为一个瓶颈。

分支预测

分支预测器没有专门识别循环。带有许多分支循环的预测逊色于之前的处理器。分支历史模式表与分支缓冲可能比之前的处理器大,但误预测仍然常见。对适合μop缓存的代码,误预测惩罚要更小。

内存访问

Sandy Bridge有两个内存读端口,而之前的处理器仅有一个。这是一个重要的改进。在最大内存带宽利用率时,缓存库冲突相当常见。

Ivy Bridge预取指令有一个严重的问题,它极慢。

多线程

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

文献

"Intel 64 and IA-32 Architectures Optimization Reference Manual". Intel 2011.

猜你喜欢

转载自blog.csdn.net/wuhui_gdnt/article/details/84872258