(但不是adc al,imm8短格式编码,或其他al / ax / eax / rax,imm8 / 16/32/32短格式,没有ModRM.我的答案中有更多细节.)
但是,即时0的adc特别适用于Haswell解码为只有一个uop. @BeeOnRope tested this,包括performance quirk在他的uarch-bench中的检查:https://github.com/travisdowns/uarch-bench.CI on a Haswell server的样本输出显示adc reg,0和adc reg,1或adc reg,zeroed-reg之间的差异.
(对于SBB也是如此.就我所见,在任何CPU上具有相同立即数的等效编码,ADC和SBB性能之间从来没有任何差别.)
这个针对imm = 0的优化是什么时候引入的?
我在Core 21上测试过,发现adc eax,0延迟是2个周期,与adc eax相同,3.并且,对于0和3的吞吐量测试的一些变化,循环计数是相同的,因此第一代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 rbp或sub ebp,而不是uops_issued.any是预期的2.2G.
这是非常可重复的:perf stat -r5(运行5次并显示平均方差),以及多次运行,显示循环计数可重复到1000分中的1分.1c与adc中的2c延迟会产生很大的影响比那更大的差异.
使用0以外的立即数重建可执行文件并不会改变Core 2上的时间,这是另一个没有特殊情况的强烈信号.这绝对值得测试.
我最初看的是吞吐量(在每次循环迭代之前使用xor eax,eax,让OoO exec重叠迭代),但很难排除前端效果.我想我最终通过添加单uop添加指令避免了前端瓶颈.内循环的吞吐量测试版本如下所示:
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瓶颈.我想我可以让这种情况发生,时间5 adc eax,0,因为管道的某个后期阶段不可能在不执行它的情况下抛出该uop.
Nehalem的循环缓冲区可以回收已解码的uop,并且可以避免解码背对背多uop指令的瓶颈问题.
根据我的微基准测试,其结果可以在 uops.info找到,这个优化是在Sandy Bridge( http://www.uops.info/html-tp/SNB/ADC-2055-Measurements.html)中引入的. Westmere没有做这个优化( http://www.uops.info/html-tp/WSM/ADC-2055-Measurements.html).使用Core i7-2600和Core i5-650获得数据.此外,uops.info上的数据表明,如果使用8位寄存器,则不执行优化(Sandy Bridge,Ivy Bridge,Haswell).