【问题标题】:SSE: shuffle (permutevar) 4x32 integersSSE:随机播放(permutevar)4x32 整数
【发布时间】:2019-09-25 17:59:46
【问题描述】:

我有一些代码使用 AVX2 内在 _mm256_permutevar8x32_epi32 aka vpermd 通过索引向量从输入向量中选择整数。现在我需要同样的东西,但需要 4x32 而不是 8x32。 _mm_permutevar_ps 用于浮点数,但我使用的是整数。

一个想法是_mm_shuffle_epi32,但我首先需要将我的 4x32 索引值转换为单个整数,即:

imm[1:0] := idx[31:0]
imm[3:2] := idx[63:32]
imm[5:4] := idx[95:64]
imm[7:6] := idx[127:96]

我不确定最好的方法是什么,而且我不确定这是最好的方法。我正在寻找 Broadwell/Haswell 上最有效的方法来模拟“失踪”_mm_permutevar_epi32(__m128i a, __m128i idx)。如果可能,我宁愿使用 128 位指令而不是 256 位指令(即我不想扩大 128 位输入然后缩小结果)。

【问题讨论】:

  • 在运行时生成立即数是没有用的,除非你在 JITing 新代码。立即数是一个字节,它实际上是机器代码指令编码的一部分。如果你有一个编译时常量洗牌(在内联+模板扩展之后),那就太好了,否则忘记那些洗牌。
  • 如果您已经有该格式的随机控制向量,您最好的选择可能是通过强制转换对整数数据使用vpermilps (_mm_permutevar_ps)。否则,您可以将其转换为 pshufb (_mm_shuffle_epi8) 的控制向量,但这比任何可能的额外旁路延迟延迟都要糟糕。

标签: sse simd intrinsics avx


【解决方案1】:

虽然 Peter Cordes 说 AVX 指令 vpermilps 及其内在的 _mm_permutevar_ps() 可能会完成这项工作是正确的,但如果您使用的是比 Sandy Bridge 更早的机器,SSE4.1 变体使用 pshufb效果也很好。

AVX 变种

感谢@PeterCordes

#include <stdio.h>
#include <immintrin.h>


__m128i vperm(__m128i a, __m128i idx){
    return _mm_castps_si128(_mm_permutevar_ps(_mm_castsi128_ps(a), idx));
}


int main(int argc, char* argv[]){
    __m128i a   = _mm_set_epi32(0xDEAD, 0xBEEF, 0xCAFE, 0x0000);
    __m128i idx = _mm_set_epi32(1,0,3,2);
    __m128i shu = vperm(a, idx);
    printf("%04x %04x %04x %04x\n", ((unsigned*)(&shu))[3],
                                    ((unsigned*)(&shu))[2],
                                    ((unsigned*)(&shu))[1],
                                    ((unsigned*)(&shu))[0]);
    return 0;
}

SSE4.1 变体

#include <stdio.h>
#include <immintrin.h>


__m128i vperm(__m128i a, __m128i idx){
    idx = _mm_and_si128  (idx, _mm_set1_epi32(0x00000003));
    idx = _mm_mullo_epi32(idx, _mm_set1_epi32(0x04040404));
    idx = _mm_or_si128   (idx, _mm_set1_epi32(0x03020100));
    return _mm_shuffle_epi8(a, idx);
}


int main(int argc, char* argv[]){
    __m128i a   = _mm_set_epi32(0xDEAD, 0xBEEF, 0xCAFE, 0x0000);
    __m128i idx = _mm_set_epi32(1,0,3,2);
    __m128i shu = vperm(a, idx);
    printf("%04x %04x %04x %04x\n", ((unsigned*)(&shu))[3],
                                    ((unsigned*)(&shu))[2],
                                    ((unsigned*)(&shu))[1],
                                    ((unsigned*)(&shu))[0]);
    return 0;
}

这编译下来很清晰

0000000000400550 <vperm>:
  400550:       c5 f1 db 0d b8 00 00 00         vpand  0xb8(%rip),%xmm1,%xmm1        # 400610 <_IO_stdin_used+0x20>
  400558:       c4 e2 71 40 0d bf 00 00 00      vpmulld 0xbf(%rip),%xmm1,%xmm1        # 400620 <_IO_stdin_used+0x30>
  400561:       c5 f1 eb 0d c7 00 00 00         vpor   0xc7(%rip),%xmm1,%xmm1        # 400630 <_IO_stdin_used+0x40>
  400569:       c4 e2 79 00 c1                  vpshufb %xmm1,%xmm0,%xmm0
  40056e:       c3                              retq

如果您可以保证控制索引始终为 32 位整数 0、1、2 或 3,则 AND 掩码是可选的。

【讨论】:

  • 谢谢。 Haswell 是我需要支持的最古老的架构。你觉得vpermilps在那里更好吗?
  • _mm_mullo_epi32 是 SSE4.1 pmulld。 Haswell 和更高版本的成本为 2 微秒。 (如果您在支持 AVX 的 CPU 上进行 CPU 检测并使用 AVX 版本,这没什么大不了的;他们不会使用这个版本,因为整数数据上的 vpermilps 更好。)@JohnZwinck:do 如果 AVX 实际可用,则使用它,仅作为 SSE4.1 的后备!(因此您可能应该查看没有 AVX 的代码生成,因此您会看到任何必要的 movdqa 说明) .
  • @PeterCordes 好的,您的 AVX 想法已经存在,并且归功于您自己。
  • 嗯,取决于周围的代码瓶颈,也值得考虑使用pshufb 来广播双字中的字节,但是你需要一个移位和一个 OR。虽然很好地使用 OR 来创建连续的字节掩码,但我认为它会更昂贵。
  • 根据Agner Fog's results,Penryn/Wolfdale 的吞吐量为 2c pmulld,延迟为 5c。 (它是 4 微指令,而 NHM 是 2)。你在想pmuludq,SSE2 扩大倍增。 (_mm_mul_epu32,不是mullo)。顺便说一句,vpermilps 不会对 SnB 系列造成任何延迟惩罚。随机播放没有任何 vec-int / fp 旁路延迟延迟,这就是它如此出色的原因! (也许为什么英特尔忽略了在 AVX2 或 AVX512 中添加 128 位整数版本?)有些指令仍然可以,比如 FP 混合。
【解决方案2】:

在运行时生成立即数是没有用的,除非你在 JITing 新代码。立即数是一个字节,它实际上是机器代码指令编码的一部分。如果你有一个编译时常量 shuffle(在内联 + 模板扩展之后),那就太好了,否则忘记那些将控制操作数作为整数的 shuffle1


在 AVX 之前,只有可变控制洗牌是 SSSE3 pshufb。 (_mm_shuffle_epi8)。这仍然是 AVX2 中唯一的 128 位(或通道内)integer shuffle 指令,我认为是 AVX512。

AVX1 添加了一些通道内 32 位变量随机播放,例如 vpermilps (_mm_permutevar_ps)。 AVX2 添加了车道交叉整数和 FP 洗牌,但有点奇怪的是没有 vpermd 的 128 位版本。可能是因为英特尔微架构不会因为在整数数据上使用 FP shuffle 而受到惩罚。 (这在 Sandybridge 家族中是正确的,我只是不知道这是否是 ISA 设计的部分原因)。但是你会认为他们会为vpermilps 添加__m128i 内在函数,如果这是你“应该”做的。或者编译器/内在函数设计的人不同意 asm 指令集的人?


如果您有一个 32 位索引的运行时变量向量,并且想要以 32 位粒度进行随机播放,那么到目前为止,您最好的选择是使用 AVX _mm_permutevar_ps

_mm_castps_si128( _mm_permutevar_ps (_mm_castsi128_ps(a), idx) )

至少在 Intel 上,当在 paddd 等整数指令之间使用时,它甚至不会引入任何额外的绕过延迟;即 FP shuffles 特别是(不是混合)对 Sandybridge 系列 CPU 中的整数数据使用没有任何惩罚

如果对 AMD Bulldozer 或 Ryzen 有任何惩罚,那是轻微的,而且肯定比为 (v)pshufb 计算随机控制向量的成本便宜。

使用vpermd ymm 并忽略输入和输出的高 128 位(即通过使用强制转换内在函数)在 AMD 上会很多慢(因为它的 128 位 SIMD 设计必须拆分通道-将 256 位 shuffle 转换为几个 uops),而且在 Intel 上更糟糕的是,它使延迟为 3c 而不是 1 个周期。


@Iwill 的回答显示了一种从 4x32 位双字索引向量计算 pshufb 字节索引的随机控制向量的方法。但它使用 SSE4.1 pmulld,在大多数 CPU 上是 2 微指令,很容易成为比洗牌更严重的瓶颈。 (请参阅该答案下 cmets 中的讨论。)尤其是在没有 AVX 的旧 CPU 上,其中一些 CPU 与现代 Intel 不同(Haswell 及更高版本只有 1 个 shuffle 端口并且很容易成为 shuffle 的瓶颈。IceLake 将添加另一个shuffle 端口,根据英特尔的 Sunny Cove 演示文稿。)

如果您确实必须为此编写 SSSE3 或 SSE4.1 版本,最好仍然只使用 SSSE3 并使用 pshufb 加上左移以在 0,1,2,3 中进行 ORing 之前复制 dword 中的一个字节进入低位,而不是pmulld。 SSE4.1 pmulld 是多个微指令,在一些速度较慢的pshufb 的 CPU 上甚至比 pshufb 还要糟糕。 (在只有 SSSE3 而不是 SSE4.1(即第一代 Core2)的 CPU 上,您可能根本无法从矢量化中受益,因为它的速度很慢 pshufb。)

在第二代 Core2 和 Goldmont 上,pshufb 是具有 1 个周期延迟的单微指令。在 Silvermont 和第一代 Core 2 上,它不是那么好。但总的来说,我建议 pshufb + pslld + por 为另一个 pshufb 计算控制向量如果 AVX 不可用

为随机播放做准备的额外随机播放远比在任何支持 AVX 的 CPU 上使用 vpermilps 糟糕得多。


脚注 1

您必须使用switch 或其他东西来选择具有正确编译时间常数整数的代码路径,这太可怕了;如果您甚至没有可用的 SSSE3,请仅考虑这一点。除非跳转表分支预测完美,否则它可能比标量更糟糕。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多