c++ - 如何向量化 data_i16[0 到 15]?

标签 c++ arrays sse simd lookup-tables

我正在使用the Intel Intrinsic site我不知道我想要什么样的指令组合。我想做的是

result = high_table[i8>>4] & low_table[i8&15]

两个表都是 16 位(或更多)。 shuffle 似乎是我想要的(_mm_shuffle_epi8),但是获得 8 位值对我来说不起作用。似乎没有 16 位版本,非字节版本似乎需要第二个参数作为立即值。

我应该如何实现这个?我是否为每个表调用 _mm_shuffle_epi8 两次,将其转换为 16 位并将值移动 8?如果是这样,我想查看哪条强制转换和移位指令?

最佳答案

要将传入的索引拆分为两个半字节 vector ,您需要通常的位移位和 AND。 SSE 没有 8 位移位,因此您必须使用更宽的移位和 AND 来模拟,以屏蔽移入字节顶部的位。 (因为不幸的是,对于此用例,_mm_shuffle_epi8 不会忽略高位。如果设置了顶部选择器位,则会将该输出元素清零。)

您绝对不想将传入的 i8 vector 扩展为 16 位元素;无法与 _mm_shuffle_epi8 一起使用。


AVX2 具有 vpermd :从 8x 32 位元素的 vector 中选择双字。 (只有 3 位索引,因此它不适合您的用例,除非您的半字节只有 0..7)。 AVX512BW 具有更广泛的洗牌功能,包括用于索引两个 vector 串联表的 vpermi2w,或者仅用于索引单词的 vpermw

但是对于仅使用 SSSE3 的 128 位 vector ,pshufb (_mm_shuffle_epi8) 是正确的选择。 high_table 需要两个单独的 vector ,一个用于每个单词条目的高字节,一个用于低字节。另外两个 vector 表示 low_table 的一半。

使用_mm_unpacklo_epi8_mm_unpackhi_epi8交错两个 vector 的低8字节,或两个 vector 的高8字节。这将为您提供所需的 16 位 LUT 结果,每个单词的上半部分来自高半 vector 。

即您正在使用此交错从两个 8 位 LUT 构建一个 16 位 LUT。您需要对两个不同的 LUT 重复该过程两次。


代码看起来像这样

// UNTESTED, haven't tried even compiling this.

// produces 2 output vectors, you might want to just put this in a loop instead of making a helper function for 1 vector.
// so I'll omit actually returning them.
void foo(__m128i indices)
{
   // these optimize away, only used at compile time for the vector initializers
   static const uint16_t high_table[16] = {...},
   static const uint16_t low_table[16] =  {...};

   // each LUT needs a separate vector of high-byte and low-byte parts
   // don't use SIMD intrinsics to load from the uint16_t tables and deinterleave at runtime, just get the same 16x 2 x 2 bytes of data into vector constants at compile time.
   __m128i high_LUT_lobyte = _mm_setr_epi8(high_table[0]&0xff, high_table[1]&0xff, high_table[2]&0xff, ... );
   __m128i high_LUT_hibyte = _mm_setr_epi8(high_table[0]>>8, high_table[1]>>8, high_table[2]>>8, ... );

   __m128i low_LUT_lobyte = _mm_setr_epi8(low_table[0]&0xff, low_table[1]&0xff, low_table[2]&0xff, ... );
   __m128i low_LUT_hibyte = _mm_setr_epi8(low_table[0]>>8, low_table[1]>>8, low_table[2]>>8, ... );


// split the input indexes: emulate byte shift with wider shift + AND
    __m128i lo_idx = _mm_and_si128(indices, _mm_set1_epi8(0x0f));
    __m128i hi_idx = _mm_and_si128(_mm_srli_epi32(indices, 4), _mm_set1_epi8(0x0f));

    __m128i lolo = _mm_shuffle_epi8(low_LUT_lobyte, lo_idx);
    __m128i lohi = _mm_shuffle_epi8(low_LUT_hibyte, lo_idx);

    __m128i hilo = _mm_shuffle_epi8(high_LUT_lobyte, hi_idx);
    __m128i hihi = _mm_shuffle_epi8(high_LUT_hibyte, hi_idx);

   // interleave results of LUT lookups into vectors 16-bit elements
    __m128i low_result_first  = _mm_unpacklo_epi8(lolo, lohi);
    __m128i low_result_second = _mm_unpackhi_epi8(lolo, lohi);
    __m128i high_result_first  = _mm_unpacklo_epi8(hilo, hihi);
    __m128i high_result_second = _mm_unpackhi_epi8(hilo, hihi);

    // first 8x 16-bit high_table[i8>>4] & low_table[i8&15] results
    __m128i and_first = _mm_and_si128(low_result_first, high_result_first);
    // second 8x 16-bit high_table[i8>>4] & low_table[i8&15] results
    __m128i and_second = _mm_and_si128(low_result_second, high_result_second);

    // TOOD: do something with the results.
}

在交错之前,您可以将高半部对高半部,低半部对低半部。对于指令级并行性来说,这可能会更好一些,让 AND 的执行与 shuffle 重叠。 (Intel Haswell 通过 Skylake 的随机播放吞吐量仅为 1/时钟。)

选择变量名是一件很困难的事情。有些人干脆放弃并在某些中间步骤中使用无意义的名称。

关于c++ - 如何向量化 data_i16[0 到 15]?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61436326/

相关文章:

C++ 删除引用

C++ 将属性转换为指针以减小类的大小是否有缺点?

c# - 我该如何处理这个 NullReferenceException?

performance - _mm_shuffle_epi8 内在函数的使用

c - 与 SSE 的 128 位哈希比较

assembly - 在SSE中组合前缀

c++ - 在类外定义时 undefined symbol ,在类内定义时有效

c++ - char类型声明和验证

java - 我如何将数组转换为数组列表并对其进行更改以反射(reflect)在数组中?

java - AngularJS $http get 返回 null 状态 0