仅使用 XMM 寄存器的一部分进行数据传输
Using only parts of XMM register for data transfer
我有一个汇编程序,它在内存中处理由 3 个浮点数(3*32 位)的元组组成的数据结构。我想知道是否可以使用 xmm 寄存器优化数据复制。
从内存中读取值不会有太大问题,因为我可以加载 4*32 位,但是有什么方法可以只将 xmm 寄存器的一部分写回内存吗?
我在 ISA 文档中发现的唯一一件事是您可以使用写掩码,但没有解释如何做到这一点。
只有 AVX512 有像 vmovups [rdi]{k1}, xmm0
这样的屏蔽商店。
AVX1 具有 vmaskmovps
,其工作方式基本相同,但带有向量 control-mask。 (例如 pcmpeqd xmm1,xmm1
/ psrldq xmm1, 4
)。但它会花费多个微指令。如果目标扩展到另一个页面,它会 fault-suppression ,但如果它实际上会出错,它可能效率低下。如果 16 字节的目标不跨越页面边界,甚至 cache-line 边界,那很好。 (它可能会触发虚假的缓存未命中或必须在 cache-line 拆分上重放存储 uop,即使它只是接触另一个缓存行的屏蔽部分。我忘记了并且最近没有检查过。)
你不需要 SSE2 maskmovdqu
;具有 NT-store 语义,因此它会在写入后从缓存中逐出目标。
没有屏蔽 loads/stores,如果您知道源对象不在页面末尾,您通常可以安全地加载额外数据。
您不太可能存储超过目的地的尽头而不踩到任何重要的东西。 (除非你用一个虚拟元素填充你的结构以允许这样做,或者它在一个数组中并且你无论如何都要存储下一个元素。)
您可以使用 2 个存储来写入 12 个字节,一个 8 字节和一个 4 字节。 (或者两个重叠的 8 字节存储,如果这样更容易混洗的话)。
;; SSE2
movups xmm0, [rsi] ; loads 4 bytes past the end of your object
movsd [rdi], xmm0 ; 8 byte store of the low 2 elements
unpckhpd xmm0, xmm0 ; extract the high half
movss [rdi+8], xmm0
如果要连续存储到结构数组,则可以只存储 16 字节并将其与下一个 16 字节存储重叠。请注意最后一个元素:剥离最后一次迭代。
或者用SSE4.1 unpckhpd / movss可以变成
extractps [rdi+8], xmm0, 2
(extractps
has a r/m32 destination:您不能使用它来将标量浮点数提取到另一个 XMM 寄存器,但它对于 FP 存储到内存很有用。)
如果您的代码制作了副本的副本,您可能还想分别执行这 2 次加载,因此 store-forwarding 会起作用。
您甚至可以使用 GP-integer 寄存器,如 8 + 4 字节的 RAX 和 EDX loads/stores。 (最好先进行两次加载,然后进行两次存储,以防重叠或 4k 别名,因此 CPU 不必弄清楚第二次加载是否与第一次存储重叠)。
我有一个汇编程序,它在内存中处理由 3 个浮点数(3*32 位)的元组组成的数据结构。我想知道是否可以使用 xmm 寄存器优化数据复制。 从内存中读取值不会有太大问题,因为我可以加载 4*32 位,但是有什么方法可以只将 xmm 寄存器的一部分写回内存吗? 我在 ISA 文档中发现的唯一一件事是您可以使用写掩码,但没有解释如何做到这一点。
只有 AVX512 有像 vmovups [rdi]{k1}, xmm0
这样的屏蔽商店。
AVX1 具有 vmaskmovps
,其工作方式基本相同,但带有向量 control-mask。 (例如 pcmpeqd xmm1,xmm1
/ psrldq xmm1, 4
)。但它会花费多个微指令。如果目标扩展到另一个页面,它会 fault-suppression ,但如果它实际上会出错,它可能效率低下。如果 16 字节的目标不跨越页面边界,甚至 cache-line 边界,那很好。 (它可能会触发虚假的缓存未命中或必须在 cache-line 拆分上重放存储 uop,即使它只是接触另一个缓存行的屏蔽部分。我忘记了并且最近没有检查过。)
你不需要 SSE2 maskmovdqu
;具有 NT-store 语义,因此它会在写入后从缓存中逐出目标。
没有屏蔽 loads/stores,如果您知道源对象不在页面末尾,您通常可以安全地加载额外数据。
您不太可能存储超过目的地的尽头而不踩到任何重要的东西。 (除非你用一个虚拟元素填充你的结构以允许这样做,或者它在一个数组中并且你无论如何都要存储下一个元素。)
您可以使用 2 个存储来写入 12 个字节,一个 8 字节和一个 4 字节。 (或者两个重叠的 8 字节存储,如果这样更容易混洗的话)。
;; SSE2
movups xmm0, [rsi] ; loads 4 bytes past the end of your object
movsd [rdi], xmm0 ; 8 byte store of the low 2 elements
unpckhpd xmm0, xmm0 ; extract the high half
movss [rdi+8], xmm0
如果要连续存储到结构数组,则可以只存储 16 字节并将其与下一个 16 字节存储重叠。请注意最后一个元素:剥离最后一次迭代。
或者用SSE4.1 unpckhpd / movss可以变成
extractps [rdi+8], xmm0, 2
(extractps
has a r/m32 destination:您不能使用它来将标量浮点数提取到另一个 XMM 寄存器,但它对于 FP 存储到内存很有用。)
如果您的代码制作了副本的副本,您可能还想分别执行这 2 次加载,因此 store-forwarding 会起作用。
您甚至可以使用 GP-integer 寄存器,如 8 + 4 字节的 RAX 和 EDX loads/stores。 (最好先进行两次加载,然后进行两次存储,以防重叠或 4k 别名,因此 CPU 不必弄清楚第二次加载是否与第一次存储重叠)。