assembly - 如何将(最多)16 个单字节移动到 XMM 寄存器中?

标签 assembly x86 intel sse simd

我有一个归零的 128 位寄存器,我想左移并添加一个字节。我可以通过以下方式改变它:

pslldq xmm0, 1 

...但现在我想将 al 复制到空白处。像这样的东西:

or xmm0, al

这当然行不通。我只希望最低 8 位受影响。这将处于一个循环中,其中连续的 al 值将用于填充寄存器。所以我需要某种 mov 指令或其他替代方案。

理想的情况是用一条指令左移 8 位并插入,但我认为这种情况不存在。

我花了很多时间翻遍 x86-64 指令集数据,但找不到任何可以让我做我想做的事情的东西。可以吗?

更新:尝试pinsrb后,我发现代码逻辑有错误。 pinsrb 会很棒,但不幸的是它只能使用立即索引,而不能使用寄存器。

我正在从非连续位置获取字节,因此我认为我需要一次获取一个字节。字节数可以是 1 到 16 之间的任意值。我抓取的第一个字节应该以 xmm0 的最低字节结束,下一个字节进入下一个最低字节,依此类推。

最佳答案

Intel's intrinsics guide 对于查找向量指令很有用。它列出了 asm 助记符以及内在函数(您可以通过助记符而不是内在函数进行搜索,因为搜索会匹配条目的整个文本)。

英特尔的 PDF 引用手册也有索引。 insn set ref 手册是第 2 卷。请参阅 标签 wiki 中英特尔手册的链接。

<小时/> SSE4.1 PINSRB 可以完全按照您的要求进行操作,但这将在 Haswell 及更高版本上的每个时钟一次洗牌上产生瓶颈,无法实现每个时钟吞吐量 2 个负载。 (每个 pinrsb xmm、[mem]、imm8 2 个 uops,其中一个用于端口 5,一个用于负载端口)。

您不需要向左移动向量,因为带有合并指令 (PINSR*) 的整数 -> 向量插入采用插入位置的索引。 (并且已经需要洗牌微指令,因此每次使用相同的位置并移动向量对性能没有好处。)

对于这个问题:将16个字节单独插入到一个向量中并不是最有效的做法。将它们以 4 或 8 为一组组装在整数寄存器中可能是更好的方法。

;; b0 .. b15 are whatever addressing mode you want.
;; if you could get more than 1 of b0..b15 with a single vector load (i.e. there is some locality in the source bytes)
;; then DON'T DO THIS: do vector loads and shuffle + combine (pshufb if needed)

movzx  eax, byte [b2]   ; break the
mov    ah,  byte [b3]
shl    eax, 16         ; partial-reg merge is pretty cheap on SnB/IvB, but very slow on Intel CPUs before Sandybridge.  AMD has no penalty, just (true in this case) dependencies
mov    al,  byte [b0]
mov    ah,  byte [b1]
    ;; 5 uops to load + merge 4 bytes into an integer reg, plus 2x merging costs
movd   xmm0, eax      # cheaper than pinsrd xmm0, edx, 0.  Also zeros the rest of the vector

;alternative strategy using an extra OR, probably not better anywhere: I don't think merging AL and AH is cheaper than merging just AH
;two short dep chains instead of one longer one isn't helpful when we're doing 16 bytes
movzx  eax, byte [b4]
mov    ah,  byte [b5]
movzx  edx, byte [b6]
mov    dh,  byte [b7]
shl    edx, 16
or     edx, eax
pinsrd xmm0, edx, 1

;; Then repeat for the next two dwords.
...
pinsrd xmm0, edx, 2

...
pinsrd xmm0, edx, 3

您甚至可以继续使用 movq/pinsrq 的整数寄存器,但有 4 个独立的 dep 链,每个 dep 链只有一个 shl整数 reg 可能更好。

更新:Haswell/Skylake 上的 AH 合并并非免费。合并 uop 甚至可能需要自己在一个周期中发出(即使用前端发出带宽的 4 个插槽。)请参阅 How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent

对于其他 uarches: Why doesn't GCC use partial registers? 。特别是在 AMD 和 Silvermont 上,部分注册表写入依赖于完整注册表。这正是我们想要的吞吐量;没有额外的合并uop。 (除 Intel P6 系列及其 Sandybridge 系列后代之外的任何产品都是这种情况,其中部分寄存器重命名有时很有用,但在这种情况下是有害的。)

<小时/>

如果您不能假设 SSE4,那么您可以使用pinsrw (SSE2)。或者,最好将 movd 和 shuffle 向量与 PUNPCKLDQ / PUNPCKLDQD 一起使用。 (该链接是英特尔手册中的 HTML 摘录)。

请参阅 Agner Fog's Optimizing Assembly guide(和指令表/微体系结构指南)来决定什么样的指令序列实际上是好的。

关于assembly - 如何将(最多)16 个单字节移动到 XMM 寄存器中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39552813/

相关文章:

x86 - 访问代码段时的权限级别检查

c - 使用进位标志的多字加法

performance - 快速硬件整数除法

c - 将FPU与C内联汇编一起使用

linux - 了解 ELF64 文本/数据段布局/填充

assembly - 英特尔和 AT&T 汇编语法在 TextMate 中突出显示

c++ - 如何在汇编代码中查找方法

assembly - 在不同处理器(x86程序集)上运行代码

assembly - INT 13h 无法读取超出特定扇区的数据

caching - L4缓存发生了什么?