SSE2 packed 8-bit integer signed multiply (high-half):将一个 m128i(16x8 位)分解为两个 m128i(每个 8x16)并重新打包
SSE2 packed 8-bit integer signed multiply (high-half): Decomposing a m128i (16x8 bit) into two m128i (8x16 each) and repack
我正在尝试将每个字节乘以两个 m128i
字节(8 位有符号整数)。
这里的问题是溢出。我的解决方案是将这些 8 位有符号整数存储为 16 位有符号整数,相乘,然后将整个东西打包成一个 m128i
的 16 x 8 位整数。
这是我制作的 __m128i mulhi_epi8(__m128i a, __m128i b)
仿真:
inline __m128i mulhi_epi8(__m128i a, __m128i b)
{
auto a_decomposed = decompose_epi8(a);
auto b_decomposed = decompose_epi8(b);
__m128i r1 = _mm_mullo_epi16(a_decomposed.first, b_decomposed.first);
__m128i r2 = _mm_mullo_epi16(a_decomposed.second, b_decomposed.second);
return _mm_packs_epi16(_mm_srai_epi16(r1, 8), _mm_srai_epi16(r2, 8));
}
decompose_epi8
以非simd方式实现:
inline std::pair<__m128i, __m128i> decompose_epi8(__m128i input)
{
std::pair<__m128i, __m128i> result;
// result.first => should contain 8 shorts in [-128, 127] (8 first bytes of the input)
// result.second => should contain 8 shorts in [-128, 127] (8 last bytes of the input)
for (int i = 0; i < 8; ++i)
{
result.first.m128i_i16[i] = input.m128i_i8[i];
result.second.m128i_i16[i] = input.m128i_i8[i + 8];
}
return result;
}
此代码运行良好。我现在的目标是实现这个 for 循环的 simd 版本。我查看了 Intel Intrinsics Guide 但找不到执行此操作的方法。我猜 shuffle 可以解决这个问题,但我很难将其概念化。
由于要进行有符号乘法,需要将每个字节符号扩展为16bit字,或者将它们移到每个16bit字的上半部分。由于之后将结果打包在一起,因此可以将输入拆分为奇数字节和偶数字节,而不是高半部分和低半部分。 然后奇数字节的符号扩展可以通过将所有 16 位部分算术右移来完成您可以通过屏蔽掉偶数字节来提取奇数字节,并得到偶数字节,您可以将所有 16 位部分向左移动(都需要乘以 _mm_mulhi_epi16
)。
以下内容适用于 SSE2:
__m128i mulhi_epi8(__m128i a, __m128i b)
{
__m128i mask = _mm_set1_epi16(0xff00);
// mask higher bytes:
__m128i a_hi = _mm_and_si128(a, mask);
__m128i b_hi = _mm_and_si128(b, mask);
__m128i r_hi = _mm_mulhi_epi16(a_hi, b_hi);
// mask out garbage in lower half:
r_hi = _mm_and_si128(r_hi, mask);
// shift lower bytes to upper half
__m128i a_lo = _mm_slli_epi16(a,8);
__m128i b_lo = _mm_slli_epi16(b,8);
__m128i r_lo = _mm_mulhi_epi16(a_lo, b_lo);
// shift result to the lower half:
r_lo = _mm_srli_epi16(r_lo,8);
// join result and return:
return _mm_or_si128(r_hi, r_lo);
}
注意:以前的版本使用移位来对奇数字节进行符号扩展。在大多数 Intel CPU 上,这会增加 P0 的使用(也需要用于乘法)。位逻辑可以在更多的端口上运行,所以这个版本应该有更好的吞吐量。
我正在尝试将每个字节乘以两个 m128i
字节(8 位有符号整数)。
这里的问题是溢出。我的解决方案是将这些 8 位有符号整数存储为 16 位有符号整数,相乘,然后将整个东西打包成一个 m128i
的 16 x 8 位整数。
这是我制作的 __m128i mulhi_epi8(__m128i a, __m128i b)
仿真:
inline __m128i mulhi_epi8(__m128i a, __m128i b)
{
auto a_decomposed = decompose_epi8(a);
auto b_decomposed = decompose_epi8(b);
__m128i r1 = _mm_mullo_epi16(a_decomposed.first, b_decomposed.first);
__m128i r2 = _mm_mullo_epi16(a_decomposed.second, b_decomposed.second);
return _mm_packs_epi16(_mm_srai_epi16(r1, 8), _mm_srai_epi16(r2, 8));
}
decompose_epi8
以非simd方式实现:
inline std::pair<__m128i, __m128i> decompose_epi8(__m128i input)
{
std::pair<__m128i, __m128i> result;
// result.first => should contain 8 shorts in [-128, 127] (8 first bytes of the input)
// result.second => should contain 8 shorts in [-128, 127] (8 last bytes of the input)
for (int i = 0; i < 8; ++i)
{
result.first.m128i_i16[i] = input.m128i_i8[i];
result.second.m128i_i16[i] = input.m128i_i8[i + 8];
}
return result;
}
此代码运行良好。我现在的目标是实现这个 for 循环的 simd 版本。我查看了 Intel Intrinsics Guide 但找不到执行此操作的方法。我猜 shuffle 可以解决这个问题,但我很难将其概念化。
由于要进行有符号乘法,需要将每个字节符号扩展为16bit字,或者将它们移到每个16bit字的上半部分。由于之后将结果打包在一起,因此可以将输入拆分为奇数字节和偶数字节,而不是高半部分和低半部分。 然后奇数字节的符号扩展可以通过将所有 16 位部分算术右移来完成您可以通过屏蔽掉偶数字节来提取奇数字节,并得到偶数字节,您可以将所有 16 位部分向左移动(都需要乘以 _mm_mulhi_epi16
)。
以下内容适用于 SSE2:
__m128i mulhi_epi8(__m128i a, __m128i b)
{
__m128i mask = _mm_set1_epi16(0xff00);
// mask higher bytes:
__m128i a_hi = _mm_and_si128(a, mask);
__m128i b_hi = _mm_and_si128(b, mask);
__m128i r_hi = _mm_mulhi_epi16(a_hi, b_hi);
// mask out garbage in lower half:
r_hi = _mm_and_si128(r_hi, mask);
// shift lower bytes to upper half
__m128i a_lo = _mm_slli_epi16(a,8);
__m128i b_lo = _mm_slli_epi16(b,8);
__m128i r_lo = _mm_mulhi_epi16(a_lo, b_lo);
// shift result to the lower half:
r_lo = _mm_srli_epi16(r_lo,8);
// join result and return:
return _mm_or_si128(r_hi, r_lo);
}
注意:以前的版本使用移位来对奇数字节进行符号扩展。在大多数 Intel CPU 上,这会增加 P0 的使用(也需要用于乘法)。位逻辑可以在更多的端口上运行,所以这个版本应该有更好的吞吐量。