哪个英特尔微体系结构引入了ADC reg,0单Uop特殊情况?

问题描述 投票:6回答:2

Haswell及更早版本的ADC通常为2 uops,具有2个周期延迟,因为Intel uops传统上只能有2个输入(https://agner.org/optimize/)。 Broadwell / Skylake后来有单uop ADC / SBB / CMOV,在Haswell在某些情况下为FMA和micro-fusion of indexed addressing modes引入了3输入uop。

(但不是adc al, imm8短格式编码,或其他al / ax / eax / rax,imm8 / 16/32/32短格式,没有ModRM。我的答案中有更详细的信息。)

但是,具有0的adc特别适用于Haswell解码为仅一个uop。 @BeeOnRope tested this,并在他的uarch-bench中包括了对这个performance quirk的支票:https://github.com/travisdowns/uarch-benchCI on a Haswell server的样本输出显示adc reg,0adc reg,1adc reg,zeroed-reg之间的差异。

(对于SBB也是如此。就我所见,在任何CPU上具有相同立即数的等效编码,ADC和SBB性能之间从来没有任何差别。)


这个针对imm=0的优化是什么时候引入的?

我在Core 21上测试,发现adc eax,0延迟是2个周期,与adc eax,3相同。并且对于使用03的吞吐量测试的一些变化,循环计数是相同的,因此第一代Core 2(Conroe / Merom)不进行此优化。

回答这个问题的最简单方法可能是在Sandybridge系统上使用我的测试程序,看看adc eax,0是否比adc eax,1更快。但基于可靠文档的答案也可以。

(顺便说一句,如果有人可以访问Sandybridge上的perf计数器,你也可以通过运行@ BeeOnRope的测试代码来清除Is performance reduced when executing loops whose uop count is not a multiple of processor width?的神秘面孔。或者我在不再使用的SnB上观察到的性能急剧下降仅仅是因为un - 层压与普通的uops不同?)


脚注1:我在运行Linux的Core 2 E6600(Conroe / Merom)上使用了这个测试程序。

;; NASM / YASM
;; assemble / link this into a 32 or 64-bit static executable.

global _start
_start:
mov     ebp, 100000000

align 32
.loop:

    xor  ebx,ebx  ; avoid partial-flag stall but don't break the eax dependency
%rep 5
    adc    eax, 0   ; should decode in a 2+1+1+1 pattern
    add    eax, 0
    add    eax, 0
    add    eax, 0
%endrep

    dec ebp       ; I could have just used SUB here to avoid a partial-flag stall
    jg .loop


%ifidn __OUTPUT_FORMAT__, elf32
   ;; 32-bit sys_exit would work in 64-bit executables on most systems, but not all.  Some, notably Window's subsystem for Linux, disable IA32 compat
    mov eax,1
    xor ebx,ebx
    int 0x80     ; sys_exit(0) 32-bit ABI
%else
    xor edi,edi
    mov eax,231   ; __NR_exit_group  from /usr/include/asm/unistd_64.h
    syscall       ; sys_exit_group(0)
%endif

Linux perf在像Core 2这样的旧CPU上运行得不好(它不知道如何访问像uops这样的所有事件),但它确实知道如何读取硬件计数器的周期和指令。这就足够了。

我用它构建和描述了这个

 yasm -felf64 -gdwarf2 testloop.asm
 ld -o testloop-adc+3xadd-eax,imm=0 testloop.o

    # optional: taskset pins it to core 1 to avoid CPU migrations
 taskset -c 1 perf stat -e task-clock,context-switches,cycles,instructions ./testloop-adc+3xadd-eax,imm=0

 Performance counter stats for './testloop-adc+3xadd-eax,imm=0':

       1061.697759      task-clock (msec)         #    0.992 CPUs utilized          
               100      context-switches          #    0.094 K/sec                  
     2,545,252,377      cycles                    #    2.397 GHz                    
     2,301,845,298      instructions              #    0.90  insns per cycle        

       1.069743469 seconds time elapsed

0.9 IPC是一个有趣的数字。

这与我们期望的静态分析有2 uop / 2c延迟adc(5*(1+3) + 3) = 23指令在循环中,5*(2+3) = 25循环的延迟=循环每循环迭代。 23/25 = 0.92。

Skylake的赔率为1.15。 (5*(1+3) + 3) / (5*(1+3)) = 1.15,即额外的.15来自xor-zero和dec / jg,而adc / add链每个时钟正好以1 uop运行,在延迟方面存在瓶颈。我们期望这个1.15整体IPC在任何其他uarch上也具有单周期延迟adc,因为前端不是瓶颈。 (有序Atom和P5 Pentium会略低,但xor和dec可以与adc配对或在P5上添加。)

在SKL上,uops_issued.any = instructions = 2.303G,确认adc是单个uop(它总是在SKL上,不管眼前的价值是多少)。偶然的是,jg是新缓存行中的第一条指令,因此它不会与SKL上的dec进行宏观融合。用dec rbpsub ebp,1代替,uops_issued.any是预期的2.2G。

这是非常可重复的:perf stat -r5(运行5次并显示平均值+方差),以及多次运行,显示循环计数可重复到1000分中的1分.1c与adc中的2c延迟会产生更大的差异比起那个来说。

使用0以外的直接重建可执行文件并不会改变Core 2上的时间,这是另一个没有特殊情况的强烈信号。这绝对值得测试。


我最初看的是吞吐量(在每次循环迭代之前使用xor eax,eax,让OoO exec重叠迭代),但很难排除前端效果。我想我最终通过添加单uop add指令避免了前端瓶颈。内循环的吞吐量测试版本如下所示:

    xor  eax,eax  ; break the eax and CF dependency
%rep 5
    adc    eax, 0   ; should decode in a 2+1+1+1 pattern
    add    ebx, 0
    add    ecx, 0
    add    edx, 0
%endrep

这就是延迟测试版看起来有点奇怪的原因。但无论如何,请记住Core2没有解码的uop缓存,并且其循环缓冲区处于预解码阶段(在找到指令边界之后)。 4个解码器中只有1个可以解码多uop指令,因此adc在前端是多uop瓶颈。我想我可以用times 5 adc eax, 0让这种情况发生,因为管道的后期阶段不太可能在没有执行它的情况下抛出那个uop。

Nehalem的循环缓冲区可以回收已解码的uop,并且可以避免解码背对背多uop指令的瓶颈问题。

performance assembly x86 intel micro-optimization
2个回答
3
投票

根据我的微基准测试,其结果可以在uops.info上找到,这个优化是在Sandy Bridge(http://uops.info/html-tp/SNB/ADC_R64_I8-Measurements.html)中引入的。 Westmere不做这种优化(http://uops.info/html-tp/WSM/ADC_R64_I8-Measurements.html)。使用Core i7-2600和Core i5-650获得数据。

此外,uops.info上的数据表明,如果使用8位寄存器(Sandy BridgeIvy BridgeHaswell),则不执行优化。


2
投票

它不在Nehalem上,但在IvyBridge上。所以它在Sandybridge或IvB都是新的。

我的猜测是Sandybridge,因为这是对解码器的一次重大重新设计(产生多达4个uop,而不是Core2 / Nehalem中可能的4 + 1 + 1 + 1模式),并且依旧指示如果下一个指令是add,它们是组中的最后一个,可以宏观融合(如subjcc)。

值得注意的是,我认为SnB解码器还会在立即计数移位中查看imm8以检查它是否为零,而不是仅在执行单元2中执行此操作。

到目前为止硬数据:

  • Broadwell和后来(以及AMD和Silvermont / KNL)不需要这种优化,adc r,immadc r,r总是1 uop,除了AL / AX / EAX / RAX imm short form1
  • Haswell做了这个优化:adc reg,0是1 uop,adc reg,1是2。
  • IvyBridge i7-3630QM进行了这种优化(感谢@DavidWohlferd)。
  • 珊迪大桥 ???
  • Nehalem i7-820QM没有,adcadd慢,无论imm。
  • Core 2 E6600(Conroe / Merom)也没有。
  • 可以安全地假设Pentium M和之前没有。

脚注1:在Skylake上,没有ModR / M字节的al / ax / eax / rax,imm8 / 16/32/32短格式编码仍然可以解码为2 uop,即使立即数为零。例如,adc eax, strict dword 015 00 00 00 00)的速度是83 d0 00的两倍。两个uops都处于延迟的关键路径上。

看起来英特尔忘记更新其他直接形式的adcsbb的解码! (这同样适用于ADC和SBB。)

默认情况下,汇编程序将使用短格式表示不适合imm8的immediates,因此例如adc rax, 12345汇编为48 15 39 30 00 00而不是单字节较大的单uop格式,这是除累加器之外的寄存器的唯一选项。

adc rcx, 12345而不是RAX延迟上遇到瓶颈的循环运行速度是原来的两倍。但adc rax, 123不受影响,因为它使用单个uop的adc r/m64, imm8编码。


脚注2:如果后来的指令从INC instruction vs ADD 1: Does it matter?读取标志,如果imm8为0,则请参阅shl r/m32, imm8以获取英特尔优化手册中关于Core2停止前端的报价。(与隐含的1操作码相反,解码器始终知道写标志。)

但SnB家族并不这样做;解码器显然检查imm8以查看指令是否无条件地写入标记或是否使它们保持不变。因此,检查imm8是SnB解码器已经做过的事情,并且可以有效地为adc省略添加该输入的uop,只留下添加CF到目的地。

© www.soinside.com 2019 - 2024. All rights reserved.