代码之家  ›  专栏  ›  技术社区  ›  Peter Cordes

哪种英特尔微体系结构引入了ADC reg,0单uop特例?

  •  6
  • Peter Cordes  · 技术社区  · 6 年前

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

    (但是BDW/SKL仍然使用2 uops作为 adc al, imm8 短格式编码,或其他al/ax/eax/rax、imm8/16/32/32短格式,无ModRM。我的回答中有更多细节。)

    但是 adc 立即数为0的特殊大小写在Haswell上,只能作为单个uop解码。 @BeeOnRope tested this ,还包括一张支票 performance quirk 在他的衣帽间长凳上: https://github.com/travisdowns/uarch-bench . 从Haswell服务器上的CI输出的示例显示 adc reg,0 adc reg,1 adc reg,zeroed-reg .

    (但仅适用于32或64位操作数大小,而不是 adc bl,0 . 所以使用32位 when using adc on a setcc result 将两个条件合并为一个分支。)

    SBB也是。据我所见,在任何CPU上,ADC和SBB的性能都没有任何区别,对于具有相同即时值的等效编码。


    这是什么时候的优化 imm=0 介绍?

    我在核心2测试过 ,并发现 adc eax,0 延迟为2个周期,与 adc eax,3 . 并且对于吞吐量测试的一些变化,循环计数是相同的 0 与。 3 ,所以第一代Core2(Conroe/Merom)没有进行这种优化。

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


    脚注1 :我在运行Linux的Core2E6600(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 在旧的CPU(如Core2)上工作得不太好(它不知道如何访问UOP之类的所有事件),但它知道如何读取HW计数器的周期和指令。够了。

    我用

     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.9ipc是这里有趣的数字。

    这就是我们从静态分析中所期望的2uop/2c延迟 模数转换器 : (5*(1+3) + 3) = 23 循环中的指令, 5*(2+3) = 25 延迟周期=每个循环迭代的周期。23/25=0.92。

    天湖1点15分。 (5*(1+3) + 3) / (5*(1+3)) = 1.15 ,也就是说,额外的.15是来自xor zero和dec/jg,而adc/add链的运行速度正好是每时钟1 uop,延迟受到限制。我们期望在任何其他单周期延迟的uarch上使用1.15的IPC 模数转换器 因为前端不是瓶颈。(按顺序,Atom和P5 Pentium将略低,但xor和dec可以与adc或附加P5配对。)

    在SKL上, uops_issued.any = instructions =2.303G,确认 模数转换器 是单uop(不管immediate的值是多少,它总是在SKL上)。碰巧, jg 是新缓存线中的第一条指令,因此它不与 dec 在斯科尔。与 dec rbp sub ebp,1 相反, uops U发布。任何 是预期的2.2G。

    这是极其可重复的: perf stat -r5 (运行5次,显示平均值+方差),多次运行,显示循环计数可重复到1000分之一。1c与2c的延迟 模数转换器 会使 许多的 更大的差别。

    使用除 不会改变时间 完全 在核心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指令,因此 模数转换器 前端的多uop瓶颈。我想我可以让它发生 times 5 adc eax, 0 ,因为管道的某个后期阶段不太可能在不执行的情况下抛出该uop。

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

    2 回复  |  直到 5 年前
        1
  •  4
  •   Andreas Abel    5 年前

    根据我的微博客,结果可以在 uops.info ,此优化方法是通过砂桥引入的( http://uops.info/html-tp/SNB/ADC_R64_I8-Measurements.html ). Westmere不做这种优化( http://uops.info/html-tp/WSM/ADC_R64_I8-Measurements.html ). 数据是用i7-2600和i5-650核获得的。

    此外,关于 计量单位信息 显示如果使用8位寄存器,则不执行优化( Sandy Bridge , Ivy Bridge , Haswell ).

        2
  •  3
  •   Peter Cordes    5 年前

    尼哈琳身上没有,但在艾维布里奇身上。所以无论是桑迪布里奇还是IvB都是新的。

    我猜是桑迪布里奇干的 ,因为这是对解码器的重大重新设计(产生多达4个总UOP,而不是像Core2/Nehalem中可能出现的4+1+1+1这样的模式),并坚持可以宏融合的指令(如 add sub )如果它们是一组中的最后一个,以防下一个指令是 jcc .

    重要的是,我认为SnB解码器也会在imm8的立即计数移位中检查它是否为零,而不是只在执行单元中这样做 .

    迄今为止的硬数据 :

    • Broadwell和更高版本(以及AMD和Silvermont/KNL)不需要这种优化, adc r,imm adc r,r 总是1个uop, 除了AL/AX/EAX/RAX imm缩写 在布罗德韦尔/天空湖。
    • 哈斯韦尔做了这个优化: adc reg,0 是1uop, adc reg,1 是2。 对于32位和64位操作数大小,而不是8位。
    • IvyBridge i7-3630QM进行了此优化(感谢@DavidWohlferd)。
    • 桑迪布里奇???
    • Nehalem i7-820QM公司 , adc 添加 不考虑imm。
    • 核心2 E6600(Conroe/Merom)也没有。
    • 可以肯定的是,奔腾M和更早的版本没有。

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

    看起来英特尔忘记更新其他直接形式的 模数转换器 sbb ! (所有这些都同样适用于ADC和SBB。)

    默认情况下,汇编程序将对不适合imm8的即时消息使用缩写形式,例如 adc rax, 12345 集合到 48 15 39 30 00 00 而不是一个字节大的单uop形式,这是除了累加器之外的寄存器的唯一选项。

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


    脚注2 :见 INC instruction vs ADD 1: Does it matter? 引用英特尔优化手册中有关Core2在以后的指令从 shl r/m32, imm8 ,以防imm8为0。(与隐式-1操作码相反,解码器知道它总是写入标志。)

    但SnB家族并没有这么做 解码器 显然要检查imm8,看看指令是否无条件地写入标志,或者是否保持标志不变。因此,检查imm8是SnB解码器已经做的事情,并且可以有效地 模数转换器 忽略添加该输入的uop,只将CF添加到目标。