我有一个归零的128位寄存器,我想向左移位并添加一个字节。我可以转移它:
pslldq xmm0, 1
...但现在我想将al复制到空白处。类似的东西:
or xmm0, al
当然不起作用。我只希望受影响的最低8位。这将是一个循环,其中al的连续值将用于填充寄存器。所以我需要一些mov指令或其他替代方案。
理想情况是单个指令向左移8位并插入,但我不认为这样存在。
我花了很多时间在x86-64指令集数据中翻找,但找不到任何可以让我做我想做的事情。可以吗?
更新:尝试使用pinsrb后,我在代码逻辑中发现了一个错误。 pinsrb会很棒但不幸的是它只能使用立即索引而不是寄存器。
我从非连续位置获取字节,所以我认为我需要一次一个字节。字节数可以是1到16之间的任何字节。我抓取的第一个字节应该以xmm0的最低字节结束,下一个字节进入下一个字节等。
答案 0 :(得分:5)
Intel's intrinsics guide可用于查找矢量指令。它列出了asm助记符和内在函数(你可以通过助记符而不是内在函数进行搜索,因为搜索匹配条目的整个文本)。
英特尔的PDF参考手册也有一个索引。 insn set ref手册是第2卷。请参阅x86标签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(以及指令表/微指南)来确定哪些指令序列实际上是好的。