随机播放 SSE 寄存器中的偶数和奇数值

Posted

技术标签:

【中文标题】随机播放 SSE 寄存器中的偶数和奇数值【英文标题】:Shuffle even and odd vaues in SSE register 【发布时间】:2013-12-10 20:32:55 【问题描述】:

我加载了两个具有 16 位值的 SSE 128 位寄存器。这些值按以下顺序排列:

src[0] = [E_3, O_3, E_2, O_2, E_1, O_1, E_0, O_0]
src[1] = [E_7, O_7, E_6, O_6, E_5, O_5, E_4, O_4]

我想要实现的是这样的顺序:

src[0] = [E_7, E_6, E_5, E_4, E_3, E_2, E_1, E_0]
src[1] = [O_7, O_6, O_5, O_4, O_3, O_2, O_1, O_0]

您知道是否有一种好方法可以做到这一点(通过使用 SSE 内在函数直到 SSE 4.2)?

我现在卡住了,因为我无法在 128 位寄存器的上半部分和下半部分之间随机播放 16 位值。我只找到了 _mm_shufflelo_epi16_mm_shufflehi_epi16 内在函数。

更新:

感谢 Paul,我曾考虑将 Epi8 内部函数用于 16 位值。

我的解决方案如下:

shuffle_split = _mm_set_epi8(15, 14, 11, 10,  7,  6,  3,  2, 13, 12,  9,  8,  5,  4,  1,  0);

xtmp[0] = _mm_load_si128(src_vec);
xtmp[1] = _mm_load_si128(src_vec+1);
xtmp[0] = _mm_shuffle_epi8(xtmp[0], shuffle_split);
xtmp[1] = _mm_shuffle_epi8(xtmp[1], shuffle_split);

xsrc[0] = _mm_unpacklo_epi16(xtmp[0], xtmp[1]);
xsrc[0] = _mm_shuffle_epi8(xsrc[0], shuffle_split);
xsrc[1] = _mm_unpackhi_epi16(xtmp[0], xtmp[1]);
xsrc[1] = _mm_shuffle_epi8(xsrc[1], shuffle_split);

还有更好的解决方案吗?

【问题讨论】:

_mm_and_si128/_mm_srli_epi32_mm_packus_epi32 的组合就可以了 _mm_shuffle_epi8 的两个应用程序与适当的置换掩码。 对于 16 位元素,可能没有比您在更新中建议的解决方案更好的解决方案,但对于 32 位元素,有更好的解决方案,请参阅 ***.com/q/45376193/3852630 和 ***.com/a/45385216/3852630 【参考方案1】:

SSE 中的排列并不容易。有许多方法可以通过各种指令组合来实现相同的结果。不同的组合可能需要不同数量的指令、寄存器或内存访问。与其努力手动解决这样的难题,我更愿意只看 LLVM 编译器做了什么,所以我用 LLVM 的中间语言编写了一个您想要的排列的简单版本,它利用了极其灵活的向量 shuffle 指令:

define void @shuffle_even_odd(<8 x i16>* %src0) 
  %src1 = getelementptr <8 x i16>* %src0, i64 1
  %a = load <8 x i16>* %src0, align 16
  %b = load <8 x i16>* %src1, align 16
  %x = shufflevector <8 x i16> %a, <8 x i16> %b, <8 x i32> <i32 1, i32 3, i32 5, i32 7, i32 9, i32 11, i32 13, i32 15>
  %y = shufflevector <8 x i16> %a, <8 x i16> %b, <8 x i32> <i32 0, i32 2, i32 4, i32 6, i32 8, i32 10, i32 12, i32 14>
  store <8 x i16> %x, <8 x i16>* %src0, align 16
  store <8 x i16> %y, <8 x i16>* %src1, align 16
  ret void

使用 LLVM IR-to-ASM 编译器编译它:llc shuffle_even_odd.ll -o shuffle_even_odd.s,你会得到类似于以下 x86 程序集的内容:

movdqa  (%rdi), %xmm0
movdqa  16(%rdi), %xmm1
movdqa  %xmm1, %xmm2
pshufb  LCPI0_0(%rip), %xmm2
movdqa  %xmm0, %xmm3
pshufb  LCPI0_1(%rip), %xmm3
por %xmm2, %xmm3
movdqa  %xmm3, (%rdi)
pshufb  LCPI0_2(%rip), %xmm1
pshufb  LCPI0_3(%rip), %xmm0
por %xmm1, %xmm0
movdqa  %xmm0, 16(%rdi)

我已经排除了上面 LCPIO_* 引用的常量数据部分,但这大致转换为以下 C 代码:

void shuffle_even_odd(__m128i * src) 
    __m128i shuffle0 = _mm_setr_epi8(128, 128, 128, 128, 128, 128, 128, 128, 2, 3, 6, 7, 10, 11, 14, 15);
    __m128i shuffle1 = _mm_setr_epi8(2, 3, 6, 7, 10, 11, 14, 15, 128, 128, 128, 128, 128, 128, 128, 128);
    __m128i shuffle2 = _mm_setr_epi8(128, 128, 128, 128, 128, 128, 128, 128, 0, 1, 4, 5, 8, 9, 12, 13);
    __m128i shuffle3 = _mm_setr_epi8(0, 1, 4, 5, 8, 9, 12, 13, 128, 128, 128, 128, 128, 128, 128, 128);
    __m128i a = src[0];
    __m128i b = src[1];
    src[0] = _mm_or_si128(_mm_shuffle_epi8(b, shuffle0), _mm_shuffle_epi8(a, shuffle1));
    src[1] = _mm_or_si128(_mm_shuffle_epi8(b, shuffle2), _mm_shuffle_epi8(a, shuffle3));

这只有 4 次随机播放和 2 次按位或指令。我怀疑这些按位指令可以在 CPU 管道中比您建议的解包指令更有效地调度。

您可以在 LLVM 下载页面的“Clang Binaries”包中找到“llc”编译器:http://www.llvm.org/releases/download.html

【讨论】:

以上是关于随机播放 SSE 寄存器中的偶数和奇数值的主要内容,如果未能解决你的问题,请参考以下文章

随机播放 AVX 寄存器中的元素

如何“删除” SSE 寄存器末尾的字节?

将常量浮点数加载到 SSE 寄存器中

什么是 SSE 相当于 fstp?

在 SSE 寄存器中查找最常出现的元素

使用 x64 SSE / AVX 寄存器进行字符串反转