(one片段用于减少链接的跟踪数,实际吞吐量无关紧要,只有uop/指令重要)
这一系列的指令:
ret
ret 4
pop rcx
jmp rcx
字符串
在haswell uarch上生成以下跟踪(因为这是我所拥有的,我将坚持它,但类似的跟踪也发生在其他uarch上):https://uica.uops.info/tmp/96c206fda86a42b6abcef52bd088ac13_trace.html
为什么ret
和ret imm16
需要这么多uop?特别是与pop jmp
相比?
this post声明pop jmp
和ret
是等效的,这确实是通过基准测试这段代码所观察到的:
section .text
global _start
_start:
mov rax, 0xFFFFFFF
.lp:
call .tst
sub rax, 1
jge .lp
mov eax, 0x3C
xor edi, edi
syscall
.tst:
pop rcx
jmp rcx
; ret
型
其中,ret
版本大约需要1.3G周期,pop jmp
版本也大约需要1.3G周期
xml file with instruction info for ret
without immiediate for HSW
有这个
<architecture name="HSW">
[...]
ports="6*p06+6*p15+2*p23+2*p237+3*p4" uops="20" uops_MITE="4" uops_MS="6" uops_retire_slots="2"/>
型
这与UICA显示的顺序不完全相同,但在其他方面相同
Fog's instruction tables page 250表示ret
使用p237和p6,这与pop jmp
大致相同
此外,ret
使用的端口也是意外的,顺序如下:
我不记得uica paper的任何内容,但我最近没有重读这篇论文,只是浏览了一下,寻找关键词
我没有检查source code,因为AFAIK所有指令数据都在上面提到的XML文件中
因此,根据UICA的判断,这显然是错误的,除非有一个我不知道的原因,所以问题是,有这样的原因吗?
1条答案
按热度按时间mfpqipee1#
通常,在使用uiCA测量的块中包含
ret
是没有用的;它把你的代码当作一个循环体,不管它是否以jcc
返回到顶部结束,也不管它是否包含无条件跳转。(如果没有跳转,就好像你要展开循环体,尽管对于JCC-erratum和uop-cache目的,它只使用一个副本的地址,就好像有一个免费执行的跳转一样。uiCA使用来自https://uops.info/测量的数据。这些测量并不反映
ret
与call
配对的正常使用,只是在循环中单独使用ret
作为一般的间接跳转。ret
很难单独进行微基准测试,特别是在希望测试循环内连续代码片段的基准测试框架中,所以没有地方可以将call
放到单独的ret
中。(除了nanobench允许的“设置”代码的一部分;它可以包含在实际进入循环时跳转的ret
。但uops.info目前没有这样做; @AndreasAbel可能对改进ret
微基准测试感兴趣?)我认为Agner Fog在Haswell上对
p237 p6
端口的ret
解码为1个微融合uop的测量是正确的;这与我在Skylake 1上的测量相匹配。(除了p7几乎肯定是不正确的,可能是他的电子表格中的错别字或复制/粘贴错误。端口7只是一个存储地址执行单元,它不能运行加载。)Haswell上
ret_near
(https://uops.info/html-instr/RET_NEAR.html)的uops.info编号为uops_dispatched_port.port_6
这样的事件的计数器不会区分继续退休的uop和被丢弃的uop。这对于微基准测试大多数指令,因为基准测试循环预测完美。6*p06+6*p15+2*p23+2*p237+3*p4
。显然太高了;我们知道ret在正常使用时(与call
配对)并没有那么慢。uops.info的测试循环(例如,https://uops.info/html-tp/HSW/RET_NEAR-Measurements.html)使用RIP相关莱亚来获得“返回地址”,它用
mov
而不是push
来存储该“返回地址”。字符串
使用
mov
而不是push
意味着RSP堆栈不平衡。将显式[rsp]
与隐式修改RSP的堆栈操作(如ret
)混合使用意味着stack engine必须在每个mov
之前插入堆栈同步uop。这可能是MS(微码序列器)uop。这是此循环的真正额外成本,但也会因错误预测而放大。在Skylake上,当返回预测器堆栈为空时,
ret
福尔斯会返回到正常的间接分支预测。但我认为早期的CPU不会。(这对Spectre缓解很重要,如果在retpoline中间发生抢占或其他情况,IIRC使retpoline不是100%可靠。)uops.info在SKL上测量ret
吞吐量约为10个周期,从Haswell上的~29个周期和Sandybridge上的~31个周期提高,可能是因为这个原因。Ice Lake恢复到~31个周期,桤木Lake P-核心下降到2.17个周期/ret
,具有相同的循环。(也许冰湖放弃了正常的间接分支预测,但桤木湖把它带回来?或者发生了其他事情,比如在冰湖中的预测器中,一些分支可能彼此混淆。)无论如何,
ret
尝试使用来自下溢预测器堆栈的不存在的预测可能会导致它在大部分时间内错误预测,并带来灾难性的后果。HSW和更早版本的每个端口uop故障在www.example.com上是无稽之谈uops.info,但对于SKL和更晚版本来说有些合理。仍然可能由于堆栈同步uop和可能不完美的分支预测而膨胀,因为在桤木湖之前似乎还有一家商店在清点。Footnote 1:Skylake测量
call/ret
对我对Skylake的测量是,
call rel32
/ret
对是3个融合域uop,5个需要执行单元的未融合域uop。uops.info和Agner Fog同意,call rel32
单独是2个前端uop,作为3个未融合域uop运行。(跳转和存储地址+存储数据)。这使得ret
成为预期的1个融合域uop:一个间接跳转微融合负载。我希望Agner Fog做了类似的事情来测量ret
。我把一个或两个
call test
放在一个紧密循环(dec/jnz)中,在一个静态可执行文件中的perf stat
下运行,其中test: ret
是后来定义的。所有的ALU uop都是端口6。加载/存储是端口2,3,7的混合。(当然端口7 uop只来自call
)https://uops.info/,其中在2个前端和7个后端单独测量SKL的
ret_near
。(可能端口故障包括来自其测试循环的额外错误推测的mov
存储。)的数据
注解掉其中一条
call
指令会像预期的那样减少计数,每个iter有3个前端uop,5个后端uop。dec/jnz只是1个前端/ 1个后端uop,每次迭代总共有4个和6个uop。型
在两次单独运行中使用
uops_dispatched_port.port_0,uops_dispatched_port.port_1,uops_dispatched_port.port_5,uops_dispatched_port.port_6
和uops_dispatched_port.port_2,uops_dispatched_port.port_3,uops_dispatched_port.port_7
的计数器,我们可以看到分布:型
我不知道端口0、1和5上的几千个uop是从哪里来的。这个程序在用户空间中运行的指令都不能在它们上面运行。除非在中断发生时有某种堆栈同步。