Pet*_*des 6 performance x86 assembly intel micro-optimization
Haswell及更早版本的ADC通常为2 uops,有2个周期延迟,因为Intel uops传统上只能有2个输入(https://agner.org/optimize/).在Haswell为FMA引入3输入微指令和某些情况下的索引寻址模式的微融合之后,Broadwell/Skylake及其后来都有单uop ADC/SBB/CMOV .
(但不适用于adc al, imm8短格式编码,或其他al/ax/eax/rax,imm8/16/32/32短格式,没有ModRM.我的答案中有更详细的说明.)
但是adc,即时0是特殊的Haswell解码为只有一个uop. @BeeOnRope测试了这个,并在他的uarch-bench中包含了对这个性能怪癖的检查:https://github.com/travisdowns/uarch-bench.从输出样本CI一个的Haswell服务器上示出之间的差adc reg,0和adc reg,1或adc reg,zeroed-reg.
(对于SBB也是如此.就我所见,在任何CPU上具有相同立即数的等效编码,ADC和SBB性能之间从来没有任何差别.)
这个优化何时adc bl,0推出?
我测试了Core 2 1,发现imm=0延迟是2个周期,相同adc eax,0.同时,也是循环计数是与吞吐量测试一些变化相同的adc eax,3对比0,所以第一代的Core 2(Conroe处理器/ Merom处理器)并没有这样做优化.
回答这个问题的最简单方法可能是在Sandybridge系统上使用我的测试程序,看看是否3比它快adc eax,0.但基于可靠文档的答案也可以.
(顺便说一句,如果有人可以访问Sandybridge上的perf计数器,你还可以通过运行@ BeeOnRope的测试代码来清除在执行uop计数不是处理器宽度倍数的循环时性能降低的谜团.或者是性能我在不再工作的SnB上观察到的只是因为未分层与正常的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
Run Code Online (Sandbox Code Playgroud)
Linux adc eax,1在像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
Run Code Online (Sandbox Code Playgroud)
0.9 IPC是一个有趣的数字.
这与我们对具有2 uop/2c延迟的静态分析的期望是perf:adc循环中的指令(5*(1+3) + 3) = 23,延迟的周期=每循环迭代的周期.23/25 = 0.92.
Skylake的赔率为1.15. 5*(2+3) = 25,即额外的.15来自xor-zero和dec/jg,而adc/add链每个时钟正好以1 uop运行,这在延迟方面存在瓶颈.我们期望这个1.15整体IPC在任何其他uarch上也具有单周期延迟(5*(1+3) + 3) / (5*(1+3)) = 1.15,因为前端不是瓶颈.(有序Atom和P5 Pentium会略低,但xor和dec可以与adc配对或在P5上添加.)
在SKL上,adc= uops_issued.any= 2.303G,确认instructions是单个uop(它总是在SKL上,无论立即有什么值).偶然的,adc是新缓存行中的第一条指令,因此它不会与jgSKL上的宏指令融合.有dec或dec rbp相反,sub ebp,1是预期的2.2G.
这是非常可重复的:uops_issued.any(跑5次,并显示平均+方差),以及多个运行,表明循环数为可重复1份在1000 1C与2C潜伏期perf stat -r5会作出很多比这更大的区别.
重建可执行文件除了adc不会改变Core 2上的时间,这是另一个没有特殊情况的强烈信号.这绝对值得测试.
我最初看的是吞吐量(0在每次循环迭代之前,让OoO exec重叠迭代),但很难排除前端效果.我想,我终于做到避免通过增加单UOP前端瓶颈xor eax,eax指令.内循环的吞吐量测试版本如下所示:
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
Run Code Online (Sandbox Code Playgroud)
这就是延迟测试版看起来有点奇怪的原因.但无论如何,请记住Core2没有解码的uop缓存,并且其循环缓冲区处于预解码阶段(在找到指令边界之后).4个解码器中只有1个可以解码多uop指令,因此add前端有多个uop瓶颈.我想我可以让这种情况发生,adc因为管道的某个后期阶段不可能在不执行它的情况下抛出该uop.
Nehalem的循环缓冲区可以回收已解码的uop,并且可以避免解码背对背多uop指令的瓶颈问题.
根据我的微基准测试,其结果可以在uops.info上找到,这种优化是在 Sandy Bridge 中引入的(https://www.uops.info/html-tp/SNB/ADC_R64_0-Measurements.html)。Westmere 不会进行此优化 ( https://uops.info/html-tp/WSM/ADC_R64_0-Measurements.html )。数据是使用 Core i7-2600 和 Core i5-650 获得的。
此外,uops.info上的数据显示,如果使用 8 位寄存器(Sandy Bridge、Ivy Bridge、Haswell),则不会执行优化。
它不在 Nehalem 上,但在 IvyBridge 上。所以无论是在 Sandybridge 还是 IvB 都是新的。
我的猜测是 Sandybridge,因为这是对解码器的重大重新设计(总共产生最多 4 个 uops,而不是 Core2 / Nehalem 中可能的 4+1+1+1 等模式),并坚持以下指令如果它们是组中的最后一个,则可以进行宏熔断(例如add或sub),以防下一条指令是jcc。
重要的是,我认为 SnB 解码器还会在立即计数移位中查看 imm8 以检查它是否为零,而不是仅在执行单元2中执行此操作。
到目前为止的硬数据:
adc r,imm并且adc r,r始终为 1 uop。 除了 AL/AX/EAX/RAX immno-modrm 简写形式1是 Alder Lake 之前的 2 uops 之外。adc reg,0is 1 uop,adc reg,1is 2。 对于 32 和 64 位操作数大小,而不是 8 位。adc慢。add脚注 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。两个微指令都位于延迟的关键路径上。
看起来英特尔忘记更新其他直接形式adc和的解码sbb!(这一切都同样适用于 ADC 和 SBB。)他们最终在 Alder Lake P 核心(Golden Cove)中解决了这个问题;https://uops.info/分别测试adc AL,0和; r32 也一样。Ice/Tiger/Rocket Lake之前的Intel主流CPU(包括P6家族和Sandybridge)运行adc AL, I8adc R8l, 0adc R8l, I8adc al, 0以 2 uops 运行。(像 Silvermont 系列这样的低功耗 CPU 以 1 uop 运行。)
默认情况下,对于不适合 imm8 的立即数,汇编器将使用短格式,因此例如adc rax, 12345汇编为,48 15 39 30 00 00而不是大一字节的单 uop 形式,这是除累加器之外的寄存器的唯一选项。
瓶颈循环adc rcx, 12345而不是 RAX 延迟的循环运行速度是原来的两倍。但adc rax, 123不受影响,因为它使用的adc r/m64, imm8是单uop的编码。
脚注 2:参见INC 指令与 ADD 1:这重要吗?引用来自英特尔优化手册的内容,如果稍后的指令从 a 读取标志,并且 imm8 为 0,则 Core2 会停止前端。shl r/m32, imm8(与隐式 1 操作码相反,解码器知道隐式操作码总是写入标志。)
但 SnB 系列不这样做;解码器显然会检查 imm8 以查看该指令是否无条件写入标志或是否让它们保持不变。因此,检查 imm8 是 SnB 解码器已经做的事情,并且可以有效地adc省略添加该输入的 uop,只留下将 CF 添加到目标。
| 归档时间: |
|
| 查看次数: |
279 次 |
| 最近记录: |