'pcmpeqb' 的延迟 - 内存与 xmm 寄存器
latency for 'pcmpeqb' - memory vs xmm register
我有这两个选项:
选项 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 读取它们之前完全保存这些加载结果的任何东西)。 您几乎总是希望将常量提升到循环之外。额外指令的 1 个额外 uop 和较小的 L1i/uop 缓存占用空间是值得的。
(Nehalem 和更早的 Intel(P6 系列)如果您在一组指令中读取太多 "cold" 寄存器,则会出现寄存器读取停顿,但这只是 10 年前的 Intel CPU,不是AMD 而不是最近的英特尔。)
pcmpeqb xmm, [mem]
是 ROB 的 1 个融合域 uop(具有该寻址模式),但需要两个 RS 条目(就像一个单独的加载然后 pcmpeqb reg,reg)。当然,常量加载没有输入依赖性,因此可以立即执行,但显然会消耗缓存读取和加载吞吐量资源。
唯一的问题是这个是否在一个循环中。
微融合 ALU + 负载仍然只有从其寄存器输入到其寄存器输出的常规 ALU uop 延迟。乱序执行可以执行负载因为地址没有依赖关系,所以尽早。 https://uops.info/ 有这方面的详细数据。
但是,如果 rax
(指针)可能无法立即准备就绪,那么是的,加载使用延迟将成为关键路径的一部分。 (地址生成需要时间。)
顺便说一句,第一个选项不好;使用 xorps 或 pxor xmm0,xmm0
的零 XMM 寄存器,而不是通过加载常量。
xorps xmm0, xmm0 ; as cheap as a NOP on Sandybridge-family, or one ALU uop on Zen
pcmpeqb xmm0, [rax] ; requires alignment unless you can use vpcmpeqb
在循环外我猜你可以考虑使用全零作为内存源操作数,如果你确定前端总是一个瓶颈并且你的常量很少缓存 -小姐。然后,即使 [rax]
未对齐,您也可以将其总数减少到 2 条指令。但这会占用您本可以使用 3 字节或 4 字节指令生成的内容的数据缓存占用空间。
但是如果你确实有一些其他的常量需要超过 1 或 2 条指令来即时创建,我想不出任何真正的原因为什么最好先加载常量或取消引用寄存器. rip-relative 和 [register]
寻址模式都可以在 Sandybridge 系列的后端保持微融合。当然,如果没有 AVX,pcmpeqb
的内存操作数必须对齐,因此如果您想通过将一个负载折叠到 ALU 操作的内存源操作数中来节省前端带宽,这可能会迫使您动手。
movdqu xmm0, [rax]
pcmpeqb xmm0, [rel some_constant]
我有这两个选项:
选项 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 读取它们之前完全保存这些加载结果的任何东西)。 您几乎总是希望将常量提升到循环之外。额外指令的 1 个额外 uop 和较小的 L1i/uop 缓存占用空间是值得的。
(Nehalem 和更早的 Intel(P6 系列)如果您在一组指令中读取太多 "cold" 寄存器,则会出现寄存器读取停顿,但这只是 10 年前的 Intel CPU,不是AMD 而不是最近的英特尔。)
pcmpeqb xmm, [mem]
是 ROB 的 1 个融合域 uop(具有该寻址模式),但需要两个 RS 条目(就像一个单独的加载然后 pcmpeqb reg,reg)。当然,常量加载没有输入依赖性,因此可以立即执行,但显然会消耗缓存读取和加载吞吐量资源。
唯一的问题是这个是否在一个循环中。
微融合 ALU + 负载仍然只有从其寄存器输入到其寄存器输出的常规 ALU uop 延迟。乱序执行可以执行负载因为地址没有依赖关系,所以尽早。 https://uops.info/ 有这方面的详细数据。
但是,如果 rax
(指针)可能无法立即准备就绪,那么是的,加载使用延迟将成为关键路径的一部分。 (地址生成需要时间。)
顺便说一句,第一个选项不好;使用 xorps 或 pxor xmm0,xmm0
的零 XMM 寄存器,而不是通过加载常量。
xorps xmm0, xmm0 ; as cheap as a NOP on Sandybridge-family, or one ALU uop on Zen
pcmpeqb xmm0, [rax] ; requires alignment unless you can use vpcmpeqb
在循环外我猜你可以考虑使用全零作为内存源操作数,如果你确定前端总是一个瓶颈并且你的常量很少缓存 -小姐。然后,即使 [rax]
未对齐,您也可以将其总数减少到 2 条指令。但这会占用您本可以使用 3 字节或 4 字节指令生成的内容的数据缓存占用空间。
但是如果你确实有一些其他的常量需要超过 1 或 2 条指令来即时创建,我想不出任何真正的原因为什么最好先加载常量或取消引用寄存器. rip-relative 和 [register]
寻址模式都可以在 Sandybridge 系列的后端保持微融合。当然,如果没有 AVX,pcmpeqb
的内存操作数必须对齐,因此如果您想通过将一个负载折叠到 ALU 操作的内存源操作数中来节省前端带宽,这可能会迫使您动手。
movdqu xmm0, [rax]
pcmpeqb xmm0, [rel some_constant]