如何向量化 data_i16[0 到 15]?
How do I vectorize data_i16[0 to 15]?
我在 the Intel Intrinsic site 上,但我不知道我想要什么样的指令组合。我想做的是
result = high_table[i8>>4] & low_table[i8&15]
其中 table 都是 16 位(或更多)。随机播放似乎是我想要的 (_mm_shuffle_epi8),但是获取 8 位值对我不起作用。好像没有16位版本,非字节版本好像需要第二个参数作为立即数。
我该如何实现?我是否为每个 table 调用两次 _mm_shuffle_epi8,将其转换为 16 位并将值移动 8?如果是这样,我想查看哪个转换和移位指令?
要将传入索引拆分为两个半字节向量,您需要通常的位移位和 AND。 SSE 没有 8 位移位,因此您必须模拟更宽的位移和 AND 以屏蔽掉移入字节顶部的位。 (因为不幸的是,对于这个用例 _mm_shuffle_epi8
不会忽略高位。如果顶部 select 或位被设置,它会将输出元素归零。)
您肯定不想要将传入的i8
向量扩展到16位元素; _mm_shuffle_epi8
.
不能使用
AVX2 有 vpermd
: select 双字,来自 8x 32 位元素的向量。 (只有 3 位索引,所以它不适合你的用例,除非你的半字节只有 0..7)。 AVX512BW 具有更广泛的随机播放,包括 vpermi2w
索引到两个向量连接的 table,或者只是 vpermw
索引单词。
但是对于只有 SSSE3 的 128 位向量,是的 pshufb
(_mm_shuffle_epi8
) 是可行的方法。 high_table
需要两个单独的向量,一个用于每个词条目的高字节,一个用于低字节。 low_table.
的一半的另外两个向量
用_mm_unpacklo_epi8
和_mm_unpackhi_epi8
交错两个向量的低8字节,或者两个向量的高8字节。这将为您提供所需的 16 位 LUT 结果,每个单词的上半部分来自高半向量。
即您正在使用此交错从两个 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 在交错之前,高半对高半,低对低。对于指令级并行性来说,这可能会更好一些,让 AND 的执行与洗牌重叠。 (Intel Haswell 通过 Skylake 的洗牌吞吐量仅为 1/时钟。)
选择变量名是一件很困难的事情。有些人只是放弃并在一些中间步骤中使用无意义的名称。
我在 the Intel Intrinsic site 上,但我不知道我想要什么样的指令组合。我想做的是
result = high_table[i8>>4] & low_table[i8&15]
其中 table 都是 16 位(或更多)。随机播放似乎是我想要的 (_mm_shuffle_epi8),但是获取 8 位值对我不起作用。好像没有16位版本,非字节版本好像需要第二个参数作为立即数。
我该如何实现?我是否为每个 table 调用两次 _mm_shuffle_epi8,将其转换为 16 位并将值移动 8?如果是这样,我想查看哪个转换和移位指令?
要将传入索引拆分为两个半字节向量,您需要通常的位移位和 AND。 SSE 没有 8 位移位,因此您必须模拟更宽的位移和 AND 以屏蔽掉移入字节顶部的位。 (因为不幸的是,对于这个用例 _mm_shuffle_epi8
不会忽略高位。如果顶部 select 或位被设置,它会将输出元素归零。)
您肯定不想要将传入的i8
向量扩展到16位元素; _mm_shuffle_epi8
.
AVX2 有 vpermd
: select 双字,来自 8x 32 位元素的向量。 (只有 3 位索引,所以它不适合你的用例,除非你的半字节只有 0..7)。 AVX512BW 具有更广泛的随机播放,包括 vpermi2w
索引到两个向量连接的 table,或者只是 vpermw
索引单词。
但是对于只有 SSSE3 的 128 位向量,是的 pshufb
(_mm_shuffle_epi8
) 是可行的方法。 high_table
需要两个单独的向量,一个用于每个词条目的高字节,一个用于低字节。 low_table.
用_mm_unpacklo_epi8
和_mm_unpackhi_epi8
交错两个向量的低8字节,或者两个向量的高8字节。这将为您提供所需的 16 位 LUT 结果,每个单词的上半部分来自高半向量。
即您正在使用此交错从两个 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 在交错之前,高半对高半,低对低。对于指令级并行性来说,这可能会更好一些,让 AND 的执行与洗牌重叠。 (Intel Haswell 通过 Skylake 的洗牌吞吐量仅为 1/时钟。)
选择变量名是一件很困难的事情。有些人只是放弃并在一些中间步骤中使用无意义的名称。