我有这两个选项:
选项1:
loop:
...
movdqu xmm0, [rax]
pcmpeqb xmm0, [.zero_table]
...
...
align 16
.zero_table:
DQ 0, 0
选项2:
pxor xmm1, xmm1
loop:
...
movdqu xmm0, [rax]
pcmpeqb xmm0, xmm1
...
...
因为我们有一个循环,我认为内存操作数的延迟成本更高,所以我问这个问题...哪个选项更好并且延迟成本更低?
第二个选项显然要好得多:循环中未融合域的内容较少。因此,乱序的exec可以提前运行,并且不需要那么多的物理寄存器或加载缓冲区(或在ALU uop读取它们之前,确切地保存这些加载结果的任何内容)。 您几乎总是想从循环中提升常量。
pcmpeqb xmm, [mem]
是ROB的1个融合域uop(具有该寻址模式),但是需要两个RS条目(就像一个单独的负载,然后是pcmpeqb reg,reg)。当然,恒定负载没有输入依赖性,因此可以立即执行,但是显然会浪费缓存读取和负载吞吐量资源。
唯一的问题是这个不是是否在循环内。
[微融合ALU +负载从其寄存器输入到其寄存器输出仍然仅具有常规的ALU uop延迟。乱序的exec可以尽早执行加载,因为地址没有依赖性。 https://uops.info/上有详细的数据。
但是如果rax
(指针)可能没有立即准备好,那么负载使用等待时间就成为关键路径的一部分。 (地址生成需要时间。)
BTW,第一个选择很糟糕;零个XMM寄存器带有xorps或pxor xmm0,xmm0
,而不是通过加载常量。
xorps xmm0, xmm0 ; as cheap as a NOP on Sandybridge-family, or one ALU uop on Zen
pcmpeqb xmm0, [rax]
但是如果您确实有其他常量需要动态创建1或2条以上的指令,那么我想不出任何真正的原因,为什么先加载该常量或取消引用该寄存器会更好。 rip-相对寻址和[register]
寻址模式都可以在Sandybridge系列的后端保持微融合。当然,如果没有AVX,则必须对齐pcmpeqb
的内存操作数,因此,如果您想通过将一个负载折叠到ALU op的内存源操作数中来节省前端带宽,这可能会迫使您动手。
movdqu xmm0, [rax]
pcmpeqb xmm0, [rel some_constant]