【问题标题】:Faster way to test if xmm/ymm register is zero?更快的方法来测试 xmm/ymm 寄存器是否为零?
【发布时间】:2017-07-08 03:16:41
【问题描述】:

幸运的是PTEST 不影响进位标志,而只是设置(相当尴尬的)ZF。同时影响CF和ZF。

我想出了以下序列来测试大量值,但我对糟糕的运行时间感到不满。

              Latency / rThoughput
setup:
  xor eax,eax       ; na
  vpxor xmm0,xmm0   ; na       ;mask to use for the nand operation of ptest
work:
  vptest xmm4,xmm0  ; 3   1    ;is xmm4 alive?
  adc eax,eax       ; 1   1    ;move first bit into eax
  vptest xmm5,xmm0  ; 3   1    ;is N alive?
  adc eax,eax       ; 1   1    ;move consecutive bits into eax 

我想要eax 中所有非零寄存器的位图(显然我可以在多个寄存器中组合多个位图)。

所以每个测试都有 3+1 = 4 个周期的延迟。
其中一些可以通过在eaxecx 等之间交替来并行运行。
但它仍然很慢。
有没有更快的方法来做到这一点?

我需要连续测试 8 个 xmm/ymm 寄存器。一个字节位图中每个寄存器 1 位。

【问题讨论】:

  • 期望的输出是什么?你想有一个非零值的位图吗?或者知道任何寄存器是否非零就足够了?
  • 当然很大程度上取决于周围的代码 - 在一个循环中您将比较多少个寄存器值为零,您想对结果做什么?在不限制输出格式的情况下,检查寄存器是否为零的“最快”方法是使用PCMPEQ 指令之一与零寄存器进行比较,但这可能不会以您想要的形式留下结果。 .. 补充: fuz 领先我一分钟 :)
  • 像这样低的连续测试多少个寄存器?
  • 我正在连续测试 8 个寄存器,如果我能想到其他要测试的东西,也许稍后会测试 16 个。我已将rcl 替换为adc eax,eax,它的互惠吞吐量更快。
  • @Johan - 我添加了一个解决方案,它在向量寄存器中预先完成工作,在英特尔上应该更快,大约为 2 uops/vector,而使用adc 我得到 3 uops/vector给你的(因为PTEST 是两个微指令)。当然,您需要对其进行测量和调整以避免端口争用。

标签: assembly optimization x86 avx micro-optimization


【解决方案1】:

实际上,您现有的方法不是“相当慢”而是合理的。

当然,每个单独的测试都有 4 个周期的延迟1,但是如果您希望将结果保存在通用寄存器中,您通常需要支付 3 个周期无论如何,该移动的延迟(例如,movmskb 的延迟也为 3)。在任何情况下,您都想测试 8 个寄存器,并且您不能简单地添加延迟,因为每个寄存器大部分都是独立的,因此 uop 计数和端口使用最终可能比测试单个寄存器的延迟更重要的延迟将与其他工作重叠。

在 Intel 硬件上可能更快一点的方法是使用连续的 PCMPEQ 指令来测试多个向量,然后将结果折叠在一起(例如,如果您使用 PCMPEQQ,您实际上有 4 个四字结果并且需要将它们折叠成 1)。您可以在PCMPEQ 之前或之后弃牌,但这将有助于更多地了解您希望如何/在何处获得更好的结果。这是 8 个寄存器的未经测试的草图,xmm1-8xmm0 假定为零,xmm14pblendvb 掩码,用于选择最后一条指令中使用的备用字节。

# test the 2 qwords in each vector against zero
vpcmpeqq xmm11, xmm1, xmm0
vpcmpeqq xmm12, xmm3, xmm0
vpcmpeqq xmm13, xmm5, xmm0
vpcmpeqq xmm14, xmm7, xmm0

# blend the results down into xmm10   word origin
vpblendw xmm10, xmm11, xmm12, 0xAA   # 3131 3131
vpblendw xmm13, xmm13, xmm14, 0xAA   # 7575 7575
vpblendw xmm10, xmm10, xmm13, 0xCC   # 7531 7531

# test the 2 qwords in each vector against zero
vpcmpeqq xmm11, xmm2, xmm0
vpcmpeqq xmm12, xmm4, xmm0
vpcmpeqq xmm13, xmm6, xmm0
vpcmpeqq xmm14, xmm8, xmm0

# blend the results down into xmm11   word origin
vpblendw xmm11, xmm11, xmm12, 0xAA   # 4242 4242
vpblendw xmm13, xmm13, xmm14, 0xAA   # 8686 8686
vpblendw xmm11, xmm11, xmm13, 0xCC   # 8642 8642

# blend xmm10 and xmm11 together int xmm100, byte-wise
#         origin bytes
# xmm10 77553311 77553311
# xmm11 88664422 88664422
# res   87654321 87654321 
vpblendvb xmm10, xmm10, xmm11, xmm15

# move the mask bits into eax
vpmovmskb eax, xmm10
and al, ah

直觉是您测试每个xmm 中的每个QWORD 是否为零,为8 个寄存器提供16 个结果,然后将结果混合到xmm10 中,最后每个字节有一个结果,按顺序(所有高 QWORD 结果在所有低 QWORD 结果之前)。然后将这 16 字节掩码作为 16 位移动到 eaxmovmskb 中,最后将 eax 中每个寄存器的高位和低位 QWORD 组合起来。

在我看来,总共 16 个微指令,8 个寄存器,所以每个寄存器大约 2 个微指令。总延迟是合理的,因为它主要是“减少”类型的并行树。一个限制因素是 6 个vpblendw 操作,它们都只进入现代英特尔的端口 5。最好用VPBLENDD 替换其中的4 个,这是p015 中任何一个的“祝福”混合。这应该很简单。

所有操作都简单快速。最终的and al, ah 是部分寄存器写入,但如果您将mov 写入eax 之后可能不会受到任何惩罚。如果这是一个问题,您还可以通过几种不同的方式来执行最后一行...

这种方法还可以自然地扩展到ymm 寄存器,最后在eax 中的折叠略有不同。

编辑

稍快的结束使用打包移位来避免两条昂贵的指令:

;combine bytes of xmm10 and xmm11 together into xmm10, byte wise
; xmm10 77553311 77553311
; xmm11 88664422 88664422   before shift
; xmm10 07050301 07050301
; xmm11 80604020 80604020   after shift
;result 87654321 87654321   combined
vpsrlw xmm10,xmm10,8
vpsllw xmm11,xmm11,8
vpor xmm10,xmm10,xmm11

;combine the low and high dqword to make sure both are zero. 
vpsrldq xmm12,xmm10,64
vpand xmm10,xmm12
vpmovmskb eax,xmm10

这通过避免 2 个周期 vpblendvbor al,ah 的部分写入惩罚来节省 2 个周期,如果不需要立即使用该指令的结果,它还修复了对慢速 vpmovmskb 的依赖.


1实际上似乎只有在 Skylake 上 PTEST 的延迟为 3 个周期,之前似乎为 2。我也不确定您的 1 个周期延迟列出rcl eax, 1:根据Agner 的说法,现代英特尔似乎是3 微指令和2 周期延迟/接收吞吐量。

【讨论】:

  • pblendvb xmm10, xmm10, xmm11, xmm14 不应该是vpblendvb xmm0, xmm10, xmm11, xmm14吗?
  • 对,对!实际上 movmskb 是用来使用 xmm10 而不是 xmm0 (因为保持 xmm0 的归零方面很好,也许是为了下面的代码)。
  • @MargaretBloom - 事实上,我打算使用punpacklbw,它更自然,不需要掩码,但它只会增加端口 5 的争用。
  • @PaulR - 我认为它是对的,虽然我最初也认为or 并且最初提到了“或折叠”(但在提交之前更改了它)。如果针对xmm0 测试的寄存器为全零,则CMPEQ 返回全1,因此以eax 结尾的位为1 iffxmm 中的相应qword 为零。因此,两个位都需要为1,以便整个寄存器为零,因此是 and (这意味着位掩码有 1 表示“寄存器为零” - 我认为这也是 OPs 解决方案所做的)。
  • @Johan - 是的,当我说“xmm0 假定为零,xmm14 是 pblendvb 掩码以选择在最后一条指令。”通常我会假设代码将在循环中运行并且可以提升掩码设置,从而将掩码设置排除在外。在任何情况下,希望掩码加载是“免费的”,因为它使用了一个加载端口,否则这里没有压力。在最坏的情况下,你会得到一个糟糕的缓存未命中,所以还要考虑punpacklbw,它不需要任何掩码。
猜你喜欢
  • 2017-09-28
  • 2012-04-27
  • 1970-01-01
  • 2019-12-10
  • 1970-01-01
  • 2020-10-03
  • 2017-03-30
  • 1970-01-01
  • 2012-07-22
相关资源
最近更新 更多