如何将(最多)16个单字节移入XMM寄存器?

时间:2016-09-17 23:52:51

标签: assembly x86 intel sse simd

我有一个归零的128位寄存器,我想向左移位并添加一个字节。我可以转移它:

pslldq xmm0, 1 

...但现在我想将al复制到空白处。类似的东西:

or xmm0, al

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

理想情况是单个指令向左移8位并插入,但我不认为这样存在。

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

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

我从非连续位置获取字节,所以我认为我需要一次一个字节。字节数可以是1到16之间的任何字节。我抓取的第一个字节应该以xmm0的最低字节结束,下一个字节进入下一个字节等。

1 个答案:

答案 0 :(得分:5)

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

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

SSE4.1 PINSRB可以完全按照你的要求进行操作,但是这会在Haswell的每个时钟上进行一次洗牌时出现瓶颈,之后不会达到每时钟2个负载的吞吐量。 (每pinrsb xmm, [mem], imm8 2个uop,其中一个用于端口5,一个用于加载端口。)

你不需要向左移动向量,因为整数 - >带有合并指令的向量插入(PINSR *)获取插入位置的索引。 (并且已经需要一个随机的uop,所以每次使用相同的位置并移动向量对性能没有好处。)

对于这个问题:分别在向量中插入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的qwords的整数注册,但是每个整数reg只有4个独立的dep链和一个shl可能更好。

更新: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

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

如果你不能假设SSE4,那么你可以使用pinsrw(SSE2)。或者也许最好使用movd并将向量与PUNPCKLDQ / PUNPCKLDQD混合在一起。 (该链接指向英特尔手册中的HTML摘录)。

参见Agner Fog's Optimizing Assembly guide(以及指令表/微指南)来确定哪些指令序列实际上是好的。