【问题标题】:Permuting bytes inside SSE __m128i register在 SSE __m128i 寄存器中置换字节
【发布时间】:2014-07-06 10:43:30
【问题描述】:

我有以下问题:

__m128i 寄存器中有 16 个 8bit 值,顺序如下:

[ 1, 5, 9, 13 ] [ 2, 6, 10, 14] [3, 7, 11, 15]  [4, 8, 12, 16]

我想要实现的是有效地打乱字节以获得此排序:

[ 1, 2, 3, 4 ] [ 5, 6, 7, 8] [9, 10, 11, 12]  [13, 14, 15, 16]

它实际上类似于 4x4 矩阵转置,但对 8 位元素进行操作 在一个寄存器中。

你能指点我什么样的SSE(最好是

【问题讨论】:

  • SSSE3 有pshufb,否则会有点乱
  • 感谢您将我指向 pshufb !我想我可以从那里搬家:)
  • 除了 SSSE3 的 PSHUFB,这是英特尔在旧 CPU 上独有的,您正在查看 SSE2 PUNPCK(L/H)BW 指令的混乱序列,正如@harold 指出的那样。

标签: optimization sse simd


【解决方案1】:

你真的会想要去 SSSE3,这比尝试去更干净

您的代码将如下所示:

   #include <tmmintrin.h> // _mm_shuffle_epi8
   #include <tmmintrin.h> // _mm_set_epi8
   ...
   // check if your hardware supports SSSE3
   ...
   __m128i mask = _mm_set_epi8(15, 11, 7, 3,
                               14, 10, 6, 2,
                               13,  9, 5, 1,
                               12,  8, 4, 0);
   __m128i mtrx = _mm_set_epi8(16, 12, 8, 4,
                               15, 11, 7, 3,
                               14, 10, 6, 2,
                               13,  9, 5, 1);
   mtrx         = _mm_shuffle_epi8(mtrx, mask);

如果您真的想要 SSE2,这就足够了:
(假设我正确解释了您的初始订购)

  __m128i mask = _mm_set_epi8(0x00, 0xFF, 0x00, 0xFF,
                              0x00, 0xFF, 0x00, 0xFF,
                              0x00, 0xFF, 0x00, 0xFF,
                              0x00, 0xFF, 0x00, 0xFF);
  __m128i mtrx = _mm_set_epi8(16, 12, 8, 4,
                              15, 11, 7, 3,
                              14, 10, 6, 2,
                              13,  9, 5, 1);                                   // [1, 5, 9, 13] [2,  6, 10, 14] [3,  7, 11, 15] [ 4,  8, 12, 16]
  mtrx = _mm_packus_epi16(_mm_and_si128(mtrx, mask), _mm_srli_epi16(mtrx, 8)); // [1, 9, 2, 10] [3, 11,  4, 12] [5, 13,  6, 14] [ 7, 15,  8, 16]
  mtrx = _mm_packus_epi16(_mm_and_si128(mtrx, mask), _mm_srli_epi16(mtrx, 8)); // [1, 2, 3,  4] [5,  6,  7,  8] [9, 10, 11, 12] [13, 14, 15, 16]

或者更容易调试:

  __m128i mtrx = _mm_set_epi8(16, 12, 8, 4,
                              15, 11, 7, 3,
                              14, 10, 6, 2,
                              13, 9, 5, 1);            // [1, 5,  9, 13] [ 2,  6, 10, 14] [ 3,  7, 11, 15] [ 4,  8, 12, 16]
  __m128i mask = _mm_set_epi8(0x00, 0xFF, 0x00, 0xFF,
                              0x00, 0xFF, 0x00, 0xFF,
                              0x00, 0xFF, 0x00, 0xFF,
                              0x00, 0xFF, 0x00, 0xFF);
  __m128i temp = _mm_srli_epi16(mtrx, 8);              // [5, 0, 13,  0] [ 6,  0, 14,  0] [ 7,  0, 15,  0] [ 8,  0, 16,  0]
  mtrx         = _mm_and_si128(mtrx, mask);            // [1, 0,  9,  0] [ 2,  0, 10,  0] [ 3,  0, 11,  0] [ 4,  0, 12,  0]
  mtrx         = _mm_packus_epi16(mtrx, temp);         // [1, 9,  2, 10] [ 3, 11,  4, 12] [ 5, 13,  6, 14] [ 7, 15,  8, 16]
  temp         = _mm_srli_epi16(mtrx, 8);              // [9, 0, 10,  0] [11,  0, 12,  0] [13,  0, 14,  0] [15,  0, 16,  0]
  mtrx         = _mm_and_si128(mtrx, mask);            // [1, 0,  2,  0] [ 3,  0,  4,  0] [ 5,  0,  6,  0] [ 7,  0,  8,  0] 
  mtrx         = _mm_packus_epi16(mtrx, temp);         // [1, 2,  3,  4] [ 5,  6,  7,  8] [ 9, 10, 11, 12] [13, 14, 15, 16]

【讨论】:

  • 非常感谢您的解释!我将使用 SSE3 作为主要路径,并在没有 SSE3 的平台上回退到 SSE2 版本。
  • @user3809354:没问题!请记住,您需要在启动时调用一次 CPUID 并存储确定要运行的代码版本所需的任何内容。 MSDN 是一个很好的起点,可以查看那里有什么。您可以向上导航树以查看 SSSE3 和 SSE4 内在函数。我发现那里的东西确实可以使用示例(例如,随机播放掩码值含义),但这就是 SO 的用途。 :)
  • @user3809354:顺便说一句,我意识到您可以通过调用 _mm_srli_epi16 而不是 _mm_srli_si128 在 SSE2 版本中保存一些指令,因为它会为每个 16 位组件移入零。这避免了后面的掩码,因为在调用 pack 之前,每个 16 位组件的高字节中都需要有零。这是因为没有截断的包版本(即 _mm_packs_epi16 和 _mm_packus_epi16 分别执行有符号和无符号饱和),因此要丢弃高字节,它需要包含零。我已经更新了答案的代码以反映这一点。
  • 再次感谢!由于您的帮助,我已设法使其运行!另一方面,我必须说 SSE / AVX 指令集的“正交性”让我笑了好几次 :)
猜你喜欢
  • 2020-03-25
  • 2016-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-25
  • 1970-01-01
相关资源
最近更新 更多