【问题标题】:How to move (up to) 16 single bytes into an XMM register?如何将(最多)16 个单字节移动到 XMM 寄存器中?
【发布时间】:2017-01-25 22:35:54
【问题描述】:

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

pslldq xmm0, 1 

...但现在我想将 al 复制到空白处。比如:

or xmm0, al

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

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

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

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

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

【问题讨论】:

  • 你想要 SSE4.1 pinsrb xmm0, eax, 1,但重复 16 次很慢。不用每次都移动向量,只需将其与 16 个不同的索引一起使用即可。
  • 展开您的插入循环(保留退出测试),以便您可以使用索引 = 0、1、2、...您将插入多少总字节的时间),但这会起作用。
  • 我不能给你任何关于什么是最优的更具体的建议,因为周围的代码有太多的未知数(例如,你是否在 shuffle 吞吐量、延迟、uop 吞吐量、缓存未命中方面遇到瓶颈...您是否需要大量这些字节收集器?或者除此之外还有很多其他计算吗?)在某些情况下,将字节复制到 16B 临时数组中并从那个(例如,如果存储转发失败的延迟不是问题,并且所有这些存储都不是问题)。
  • 我认为在 Haswell 或更高版本或 AMD 上,在插入 XMM 之前在整数寄存器中进行一些合并将是一个非常好的主意。

标签: assembly x86 intel sse simd


【解决方案1】:

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

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


SSE4.1 PINSRB 可以完全按照您的要求进行,但这将成为 Haswell 上每个时钟一次 shuffle 的瓶颈,以后无法实现每个时钟吞吐量 2 次负载。 (每个pinrsb xmm, [mem], imm8 2 个微指令,其中一个用于端口 5,一个用于加载端口)。

您不需要将向量左移,因为带有合并指令的整数 -> 向量插入 (PINSR*) 为插入位置提供索引。 (并且已经需要一个 shuffle 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 链和每个整数 reg 仅一个 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

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


如果你不能假设 SSE4,那么你可以使用 pinsrw (SSE2)。或者使用movd 和随机播放向量与PUNPCKLDQ / PUNPCKLDQD 一起使用会更好。 (该链接指向英特尔手册的 HTML 摘录)。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多