如何使用 SSE 高效地执行 int8/int64 转换?
How to efficiently perform int8/int64 conversion with SSE?
我正在 SSE 类型之间实现转换,我发现为 SSE4.1 之前的目标实现 int8->int64 扩展转换很麻烦。
最简单的实现方式是:
inline __m128i convert_i8_i64(__m128i a)
{
#ifdef __SSE4_1__
return _mm_cvtepi8_epi64(a);
#else
a = _mm_unpacklo_epi8(a, a);
a = _mm_unpacklo_epi16(a, a);
a = _mm_unpacklo_epi32(a, a);
return _mm_srai_epi64(a, 56); // missing instrinsic!
#endif
}
但由于 _mm_srai_epi64
在 AVX-512 之前不存在,此时有两个选择:
- 实施
_mm_srai_epi64
,或
- 以不同的方式实施
convert_i8_i64
。
我不确定哪一个是最有效的解决方案。有什么想法吗?
解包内在函数在这里以一种有趣的方式使用。他们 "duplicate" 数据,而不是像人们期望的那样添加符号扩展。例如,在第一次迭代之前,您的寄存器中有以下
x x x x x x x x x x x x x x a b
如果将 a
和 b
转换为 16 位,您应该得到:
x x x x x x x x x x x x A a B b
这里的A
和B
是a
和b
的符号扩展,即两者都为0或-1。
而不是这个,你的代码给出
x x x x x x x x x x x x a a b b
然后通过右移将其转换为正确的结果。
但是,您不必在 "unpack" 内部函数中两次使用相同的操作数。如果你 "unpacked" 以下两个寄存器,你可以得到想要的结果:
x x x x x x x x x x x x x x a b
x x x x x x x x x x x x x x A B
即:
a = _mm_unpacklo_epi8(a, _mm_srai_epi8(a, 8));
(如果 _mm_srai_epi8
内在确实存在)
您可以将相同的想法应用到转换的最后阶段。您要 "unpack" 以下两个寄存器:
x x x x x x x x A A A a B B B b
x x x x x x x x A A A A B B B B
要获取它们,右移 32 位数据:
_mm_srai_epi32(a, 24)
_mm_srai_epi32(a, 32)
所以最后一个"unpack"是
_mm_unpacklo_epi32(_mm_srai_epi32(a, 24), _mm_srai_epi32(a, 32));
使用 SSSE3,您可以使用 pshufb
来避免大多数解包。使用 anatoly 的 a
/ A
表示法:
;; input in xmm0 ;; x x x x x x x x | x x x x x x a b
pshufb xmm0, [low_to_upper] ;; a 0 0 0 0 0 0 0 | b 0 0 0 0 0 0 0
psrad xmm0, 24 ;; A A A a 0 0 0 0 | B B B b 0 0 0 0
pshufb xmm0, [bcast_signextend]; A A A A A A A a | B B B B B B B b
如果没有 SSSE3,我认为您可以使用 PSHUFLW、PSHUFD 和 POR 来代替某些 PUNPCK 步骤。但是我想到的实际上没有比解包更好的了,除非你在 Core2 或其他慢速洗牌 CPU 上,其中 pshuflw
比 punpcklbw
.
快
我正在 SSE 类型之间实现转换,我发现为 SSE4.1 之前的目标实现 int8->int64 扩展转换很麻烦。
最简单的实现方式是:
inline __m128i convert_i8_i64(__m128i a)
{
#ifdef __SSE4_1__
return _mm_cvtepi8_epi64(a);
#else
a = _mm_unpacklo_epi8(a, a);
a = _mm_unpacklo_epi16(a, a);
a = _mm_unpacklo_epi32(a, a);
return _mm_srai_epi64(a, 56); // missing instrinsic!
#endif
}
但由于 _mm_srai_epi64
在 AVX-512 之前不存在,此时有两个选择:
- 实施
_mm_srai_epi64
,或 - 以不同的方式实施
convert_i8_i64
。
我不确定哪一个是最有效的解决方案。有什么想法吗?
解包内在函数在这里以一种有趣的方式使用。他们 "duplicate" 数据,而不是像人们期望的那样添加符号扩展。例如,在第一次迭代之前,您的寄存器中有以下
x x x x x x x x x x x x x x a b
如果将 a
和 b
转换为 16 位,您应该得到:
x x x x x x x x x x x x A a B b
这里的A
和B
是a
和b
的符号扩展,即两者都为0或-1。
而不是这个,你的代码给出
x x x x x x x x x x x x a a b b
然后通过右移将其转换为正确的结果。
但是,您不必在 "unpack" 内部函数中两次使用相同的操作数。如果你 "unpacked" 以下两个寄存器,你可以得到想要的结果:
x x x x x x x x x x x x x x a b
x x x x x x x x x x x x x x A B
即:
a = _mm_unpacklo_epi8(a, _mm_srai_epi8(a, 8));
(如果 _mm_srai_epi8
内在确实存在)
您可以将相同的想法应用到转换的最后阶段。您要 "unpack" 以下两个寄存器:
x x x x x x x x A A A a B B B b
x x x x x x x x A A A A B B B B
要获取它们,右移 32 位数据:
_mm_srai_epi32(a, 24)
_mm_srai_epi32(a, 32)
所以最后一个"unpack"是
_mm_unpacklo_epi32(_mm_srai_epi32(a, 24), _mm_srai_epi32(a, 32));
使用 SSSE3,您可以使用 pshufb
来避免大多数解包。使用 anatoly 的 a
/ A
表示法:
;; input in xmm0 ;; x x x x x x x x | x x x x x x a b
pshufb xmm0, [low_to_upper] ;; a 0 0 0 0 0 0 0 | b 0 0 0 0 0 0 0
psrad xmm0, 24 ;; A A A a 0 0 0 0 | B B B b 0 0 0 0
pshufb xmm0, [bcast_signextend]; A A A A A A A a | B B B B B B B b
如果没有 SSSE3,我认为您可以使用 PSHUFLW、PSHUFD 和 POR 来代替某些 PUNPCK 步骤。但是我想到的实际上没有比解包更好的了,除非你在 Core2 或其他慢速洗牌 CPU 上,其中 pshuflw
比 punpcklbw
.