【问题标题】:Having 4 bits, how to produce a mask for AVX register? [duplicate]有 4 位,如何为 AVX 寄存器生成掩码? [复制]
【发布时间】:2018-02-08 14:04:54
【问题描述】:

_mm256_blendv_pd() 查看位置 63、127、191 和 255 中的位。有没有一种有效的方法将 uint8_t 的 4 个低位分散到 AVX 寄存器的这些位置?

或者,是否有一种有效的方法来广播这些位,以便像 _mm256_cmp_pd() 的结果一样,每个位在 AVX 寄存器的相应 64 位组件中重复?

指令集是 AVX2(如果需要其他功能,Ryzen CPU)。

【问题讨论】:

  • 因为 63、127、191 和 255 不是 2 的幂,它们不能作为掩码来指示位位置。如果它们是位向量的索引,那么您至少有 255 位要处理。 'unit8_t' 包含 8 位(因此是 '8')所以您问是否可以用 8 位表示 255 位?这似乎不太可能。您需要先纠正问题,然后才能得到有意义的答案。
  • 如果你是非一,64、128 和 256 是 2 的幂,所以它们可能是位掩码,但 192 不符合模式(fwiw 它是 64 + 128,但那是两位)所以....
  • @DaleWilson,这是一个关于 AVX(2) 技术的问题,它操作 256 位向量。 uint8_t 最初有 4 位。我想将它们移动到指定的位置(你不明白:63、127、191 和 255 是从 0 开始的位位置,而不是掩码)到 256 位 AVX 寄存器。
  • 足够接近复制;只需省略广播到所有位的部分。 (请注意,pdep 在 Ryzen 上速度很慢)。只有 4 位确实使 LUT 具有吸引力。您可以压缩 LUT 并使用 vpmovsxbq 加载它。

标签: c++ bit-manipulation vectorization x86-64 avx2


【解决方案1】:

假设uint8_t存在于一个通用寄存器中;方法是:

  1. 使用PDEP 将四位转换为四字节(最高位)
  2. 将 4 个字节从 32 位 GPR 传输到 YMM 寄存器的低位
  3. 将值放在适当的位置(位 63、127、191、255)

所以我想出了两个版本 - 一个有内存,另一个没有:

记忆法:

.data
  ; Always use the highest bytes of a QWORD as target / 128 means 'set ZERO' 
  ddqValuesDistribution:    .byte  3,128,128,128,128,128,128,128, 2,128,128,128,128,128,128,128, 1,128,128,128,128,128,128,128, 0,128,128,128,128,128,128,128
.code
  ; Input value in lower 4 bits of EAX
  mov     edx, 0b10000000100000001000000010000000
  pdep    eax, eax, edx
  vmovd   xmm0, eax
  vpshufb ymm0, ymm0, ymmword ptr [ddqValuesDistribution]

这个在 Haswell 和 Skylake 上以 5 uOps 发布。


没有内存变量的方法(感谢@Peter Cordes 改进):

  mov  edx, 0b10000000100000001000000010000000
  pdep eax, eax, edx
  vmovd xmm0, eax 
  vpmovsxbq ymm0, xmm0

这个在 Haswell 和 Skylake(!)上以 4 uOps 出现,可以通过将 EDX 中的掩码移动到变量来进一步改进。
输出与第一个版本不同(全为与仅设置最高位)。

【讨论】:

  • @zx485: pdep 在 Ryzen 上是 6 微秒。因此,这些 uop 计数仅适用于 Intel CPU。
  • 尝试使用vpmovsxbq将每个字节的符号位复制到每个qword的高56位。
  • @PeterCordes:非常感谢。真的很棒的建议。 PDEP 在 Ryzen 上的表现如此糟糕,真可惜。
  • 你还不如去掉vpshufb这个版本;它与vpmovsxbq 相比没有优势,并且如果没有额外的洗牌就无法工作,因为vpshufb 不是车道交叉口。 (另外,我对这个问题的副本 (stackoverflow.com/questions/36488675/…) 的回答已经有 pdep / vpmovsxbq 版本。请注意,它没有设置 all 位;低 7 仍然是0. 很抱歉成为坏消息的承担者,你的好主意已经被发明出来了。不过,这是一个很酷的主意,你自己想出来真是太好了。)
  • @PeterCordes:是的。这是一个真正匹配的副本。对我不好:-/但是谢谢...但是,我会在这里提供两种解决方案,因为它没有害处,而且将来可能对其他用途有用...
【解决方案2】:

显而易见的解决方案:使用这 4 位作为查找表的索引。你已经知道了,所以让我们试试别的吧。

基于可变移位的方法:将该字节广播到每个 qword,然后将其左移 { 63, 62, 61, 60 },在 msb 中对齐右侧位。未测试,类似这样:

_mm256_sllv_epi64(_mm256_set1_epi64x(mask), _mm256_set_epi64x(63, 62, 61, 60))

作为奖励,由于负载不依赖于掩码,因此可以解除循环。

这在 Ryzen 上不一定是个好主意,从内存加载 256 位的吞吐量甚至比 vpsllvq 本身更高(与 Ryzen 上的大多数 256b 操作一样,它是 2 µops),但在这里我们也有一个vmovq(如果该字节不是来自向量寄存器)和一个宽vpbroadcastq(又是2 µops)。

根据上下文,它可能值得做或不做。视情况而定。

【讨论】:

    【解决方案3】:

    最有效的方法是使用包含 16 256 位条目的查找向量,由 uint-8 索引。

    【讨论】:

    • 这是一个很好的解决方案,但是需要 16 * 32 = 512 字节的缓存。
    • 即许多处理器上的两条缓存线——这些缓存线将是只读的,这很有帮助。我敢打赌,当您编译将位分配到 256 位所需的移位、掩码和 OR 时向量表查找将运行得更快,即使它确实需要偶尔的缓存加载。但当然,对于这类问题,“哪个更快”的唯一真正答案是分析。然而,我描述的方法在代码清晰性和可维护性方面明显胜出。
    • 在x86_64上缓存线一般是64字节,所以512字节就是8条缓存线。
    • 是的,我刚刚检查了我的事实,Ryzen 使用 64 字节缓存线,所以“四个”缓存线。尽管针对这个特定问题,我仍会坚持使用表格查找方法。
    • 当前所有的 x86 CPU 都使用 64B 高速缓存行。最后一个具有 32B 线的 CPU 是 Pentium III。我从未听说过任何 CPU 使用 256 个 byte 行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-23
    • 2011-04-25
    • 1970-01-01
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    相关资源
    最近更新 更多