可以在不使用通用寄存器的情况下将 8 位从 XMM 寄存器移动到内存吗?

Posted

技术标签:

【中文标题】可以在不使用通用寄存器的情况下将 8 位从 XMM 寄存器移动到内存吗?【英文标题】:It is possible move 8 bits from an XMM register to memory without using general purpose registers? 【发布时间】:2021-06-28 17:05:04 【问题描述】:

我需要在不使用通用寄存器的情况下将 1 个字节从 xmm 寄存器移动到内存。而且我也不能使用 SSE4.1。有可能吗?

=(

【问题讨论】:

这能回答你的问题吗? How to move 128-bit xmm directly to memory without using registers? 我不相信没有使用通用寄存器或 SSE4.1 pextrb 的方法来做到这一点。 @Alejandro 我认为你错过了 OP 只想移动 1 个字节而不是整个寄存器的部分。 @fuz: 你可以使用SSE2 maskmovdqu 掩码存储,但你不想这样做,因为 NT 语义和通常很慢,比 movd eax, xmm0 / mov [mem], al 差得多跨度> 通常您只需使用标量或可以部分重叠的向量存储来处理剩余字节。即在数组末尾结束的最终存储,即使它与一些早期存储重叠。只要总大小 >= 16 字节,它就可以工作。如果你的修改是幂等的(你可以安全地处理同一个字节两次,例如a[i] &= ~0x20而不是a[i] += 10,或者它是只写的,那么没问题。 【参考方案1】:

通常,您首先要避免这种情况。例如,您可以不进行单独的字节存储,而是进行更广泛的加载和合并(pand/pandn/por,如果您没有 pblendvb),然后存储回合并结果?

这不是线程安全的(未修改字节的非原子 RMW),但只要您知道您正在 RMW 处理的字节不会超出数组或结构的末尾,并且没有其他线程在做对同一个数组/结构中的其他元素也是一样的,这是一种正常的方法,可以将字符串中的每个小写字母都大写,而其他字节保持不变。


只能从 4、8、16、32 或 64 字节大小的向量寄存器中进行单微指令存储,除非 AVX-512BW masked stores 仅未屏蔽 1 个元素。像pextrb 这样的更窄的存储涉及一个shuffle uop 来提取要存储的2 或1 个字节。

在没有 GP 整数 regs 的情况下真正存储 1 个字节的唯一好方法是使用 SSE4.1 pextrb [mem], xmm0, 0..15 即使在当前 CPU 上立即使用 0,这仍然是随机 + 存储。 如果您可以安全地在目标位置写入 2 个字节,则 SSE2 pextrw 是可用的。

可以使用SSE2 maskmovdqu 字节掩码存储(带有0xff,0,0,... 掩码),但您不想这样做,因为它比movd eax, xmm0 / mov [mem], al 慢得多。例如在 Skylake 上,10 uops,每 6 个周期 1 个吞吐量。

如果你想在之后重新加载字节,那就更糟糕了,因为(与 AVX / AVX-512 掩码存储不同),maskmovdqu 具有类似movntps 的 NT 语义(绕过缓存,或者如果之前驱逐缓存行热)。


如果您的要求完全是人为的,而您只是想玩愚蠢的计算机技巧(避免将数据保存在寄存器中),您还可以设置暂存空间,例如在堆栈上并使用movsb 复制它:

;; with destination address already in RDI
    lea  rsi, [rsp-4]          ; scratch space in the red zone below RSP on non-Windows
    movd  [rsi], xmm0
    movsb                   ; copy a byte, [rdi] <- [rsi], incrementing RSI and RDI

这显然比正常方式慢,并且需要一个额外的寄存器 (RSI) 用于 tmp 缓冲区地址。而且您需要 RDI 中的确切目标地址,而不是 [rel foo] 静态存储或任何其他灵活的寻址模式。

pop 也可以复制 mem-to-mem,但仅适用于 16 位和 64 位操作数大小,因此无法省去您对 RSI 和 RDI 的需求。

由于上述方式需要一个额外的寄存器,因此几乎在所有方面都比普通方式更糟糕:

   movd  esi, xmm0            ; pick any register.
   mov   [rdi], sil           ; al..dl would avoid needing a REX prefix for low-8


;; or even use a register where you can read the low and high bytes separately
   movd  eax, xmm0
   mov   [rdi], al            ; no REX prefix needed, more compact than SIL
   mov   [rsi], ah            ; scatter two bytes reasonably efficiently
   shr   eax, 16              ; bring down the next 2 bytes

(在当前的 Intel CPU 上读取 AH 有一个额外的延迟周期,但这对吞吐量来说很好,而且我们无论如何都存储在这里,所以延迟不是一个很大的因素。)

xmm -> GP 整数传输在大多数 CPU 上并不慢。 (Bulldozer 系列是异常值,但它仍然与存储/重新加载相当的延迟;Agner Fog 在他的微架构指南 (https://agner.org/optimize/) 中说,他发现 AMD 的优化手动建议存储/重新加载并不快。)

很难想象movsb 会更好的情况,因为您已经需要一个免费注册,而movsb 是多个微指令。如果当前 Intel CPU 上 movd r32, xmm 的端口 0 uops 出现瓶颈,可能会出现瓶颈? (https://uops.info/)

【讨论】:

非常感谢。

以上是关于可以在不使用通用寄存器的情况下将 8 位从 XMM 寄存器移动到内存吗?的主要内容,如果未能解决你的问题,请参考以下文章

将XMM寄存器推入堆栈

X86 操作码将 xmm 寄存器移动到通用寄存器

将 Intrinsic xmm 寄存器转换为 uint8_t 数组[16]

如何在不使用 foreach 的情况下将 ArrayList 转换为强类型泛型列表?

如何将两个打包的 64 位四字加载到 128 位 xmm 寄存器中

自制反汇编工具使用实例 其二(使用xmm寄存器初始化对象,以及空的成员函数指针)