SSE 内在函数:将 32 位浮点数转换为 UNSIGNED 8 位整数
SSE intrinsics: Convert 32-bit floats to UNSIGNED 8-bit integers
使用 SSE 内在函数,我得到了一个由四个 32 位浮点数组成的向量,范围限制在 0-255 之间并四舍五入到最接近的整数。我现在想把这四个写成字节。
有一个内在的 _mm_cvtps_pi8
可以将 32 位转换为 8 位 signed int,但问题是任何超过 127 的值都会被限制为127. 我找不到任何可以限制为无符号 8 位值的指令。
我有一种直觉,我可能想要做的是 _mm_cvtps_pi16
和 _mm_shuffle_pi8
的某种组合,然后是移动指令以将我关心的四个字节放入内存。这是最好的方法吗?我要看看我是否能弄清楚如何对随机播放控制掩码进行编码。
更新:以下似乎完全符合我的要求。有没有更好的方法?
#include <tmmintrin.h>
#include <stdio.h>
unsigned char out[8];
unsigned char shuf[8] = { 0, 2, 4, 6, 128, 128, 128, 128 };
float ins[4] = {500, 0, 120, 240};
int main()
{
__m128 x = _mm_load_ps(ins); // Load the floats
__m64 y = _mm_cvtps_pi16(x); // Convert them to 16-bit ints
__m64 sh = *(__m64*)shuf; // Get the shuffle mask into a register
y = _mm_shuffle_pi8(y, sh); // Shuffle the lower byte of each into the first four bytes
*(int*)out = _mm_cvtsi64_si32(y); // Store the lower 32 bits
printf("%d\n", out[0]);
printf("%d\n", out[1]);
printf("%d\n", out[2]);
printf("%d\n", out[3]);
return 0;
}
更新 2:这是一个基于哈罗德回答的更好的解决方案:
#include <smmintrin.h>
#include <stdio.h>
unsigned char out[8];
float ins[4] = {10.4, 10.6, 120, 100000};
int main()
{
__m128 x = _mm_load_ps(ins); // Load the floats
__m128i y = _mm_cvtps_epi32(x); // Convert them to 32-bit ints
y = _mm_packus_epi32(y, y); // Pack down to 16 bits
y = _mm_packus_epi16(y, y); // Pack down to 8 bits
*(int*)out = _mm_cvtsi128_si32(y); // Store the lower 32 bits
printf("%d\n", out[0]);
printf("%d\n", out[1]);
printf("%d\n", out[2]);
printf("%d\n", out[3]);
return 0;
}
没有直接从float到byte的转换,_mm_cvtps_pi8
是复合。 _mm_cvtps_pi16
也是一个组合,在这种情况下,它只是在做一些你用洗牌撤销的无意义的事情。他们也 return 烦人 __m64
。
无论如何,我们可以转换成dwords(有符号,但没关系),然后打包(无符号)或打乱成字节。 _mm_shuffle_(e)pi8
生成一个 pshufb
,Core2 45nm 和 AMD 处理器不太喜欢它,你必须从某个地方获得掩码。
无论哪种方式,您都不必先四舍五入到最接近的整数,转换程序会这样做。至少,如果你没有弄乱舍入模式。
使用包 1:(未测试)——可能没有用,packusdw
已经输出未签名的字,但 packuswb
再次需要签名的字。保留下来是因为它在别处被引用。
cvtps2dq xmm0, xmm0
packusdw xmm0, xmm0 ; unsafe: saturates to a different range than packuswb accepts
packuswb xmm0, xmm0
movd somewhere, xmm0
使用不同的随机播放:
cvtps2dq xmm0, xmm0
packssdw xmm0, xmm0 ; correct: signed saturation on first step to feed packuswb
packuswb xmm0, xmm0
movd somewhere, xmm0
使用随机播放:(未测试)
cvtps2dq xmm0, xmm0
pshufb xmm0, [shufmask]
movd somewhere, xmm0
shufmask: db 0, 4, 8, 12, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
我们可以通过带符号饱和的第一阶段打包来解决无符号钳制问题。 [0-255]
适合带符号的 16 位 int,因此该范围内的值将保持未限定。该范围之外的值将保持在它的同一侧。因此,signed16 -> unsigned8 步骤将正确地限制它们。
;; SSE2: good for arrays of inputs
cvtps2dq xmm0, [rsi] ; 4 floats
cvtps2dq xmm1, [rsi+16] ; 4 more floats
packssdw xmm0, xmm1 ; 8 int16_t
cvtps2dq xmm1, [rsi+32]
cvtps2dq xmm2, [rsi+48]
packssdw xmm1, xmm2 ; 8 more int16_t
; signed because that's how packuswb treats its input
packuswb xmm0, xmm1 ; 16 uint8_t
movdqa [rdi], xmm0
这只需要 SSE2,packusdw
不需要 SSE4.1。
我认为这就是 SSE2 只包含从双字到字的带符号包,但包括从字到字节的带符号和无符号包的原因。 packuswd
仅在您的最终目标是 uint16_t
时才有用,而不是进一步打包。 (从那时起,您需要在将其提供给进一步的包之前屏蔽掉符号位)。
如果您确实使用了 packusdw -> packuswb
,当第一步饱和到 uint16_t
> 0x7fff 时,您会得到虚假结果。 packuswb
会将其解释为负 int16_t
并将其饱和为 0。packssdw
会将此类输入饱和到 0x7fff
,最大值 int16_t
.
(如果您的 32 位输入总是 <= 0x7fff,您可以使用其中任何一个,但是 SSE4.1 packusdw
takes more instruction bytes than SSE2 packsswd
,并且永远不会 运行s 更快。)
如果您的源值不能为负,并且您只有一个包含 4 个浮点数的向量,数量不多,您可以使用 harold 的 pshufb
想法。如果不是,您需要将负值限制为零,而不是通过将低字节改组到位来解决 运行。
使用
;; SSE4.1, good for a single vector. Use the PACK version above for arrays
cvtps2dq xmm0, xmm0
pmaxsd xmm0, zeroed-register
pshufb xmm0, [mask]
movd [somewhere], xmm0
可能比使用两个 pack
指令更有效,因为 pmax
可以 运行 在端口 1 或 5(Intel Haswell)上。 cvtps2dq
仅是端口 1,pshufb
和 pack*
仅是端口 5。
使用 SSE 内在函数,我得到了一个由四个 32 位浮点数组成的向量,范围限制在 0-255 之间并四舍五入到最接近的整数。我现在想把这四个写成字节。
有一个内在的 _mm_cvtps_pi8
可以将 32 位转换为 8 位 signed int,但问题是任何超过 127 的值都会被限制为127. 我找不到任何可以限制为无符号 8 位值的指令。
我有一种直觉,我可能想要做的是 _mm_cvtps_pi16
和 _mm_shuffle_pi8
的某种组合,然后是移动指令以将我关心的四个字节放入内存。这是最好的方法吗?我要看看我是否能弄清楚如何对随机播放控制掩码进行编码。
更新:以下似乎完全符合我的要求。有没有更好的方法?
#include <tmmintrin.h>
#include <stdio.h>
unsigned char out[8];
unsigned char shuf[8] = { 0, 2, 4, 6, 128, 128, 128, 128 };
float ins[4] = {500, 0, 120, 240};
int main()
{
__m128 x = _mm_load_ps(ins); // Load the floats
__m64 y = _mm_cvtps_pi16(x); // Convert them to 16-bit ints
__m64 sh = *(__m64*)shuf; // Get the shuffle mask into a register
y = _mm_shuffle_pi8(y, sh); // Shuffle the lower byte of each into the first four bytes
*(int*)out = _mm_cvtsi64_si32(y); // Store the lower 32 bits
printf("%d\n", out[0]);
printf("%d\n", out[1]);
printf("%d\n", out[2]);
printf("%d\n", out[3]);
return 0;
}
更新 2:这是一个基于哈罗德回答的更好的解决方案:
#include <smmintrin.h>
#include <stdio.h>
unsigned char out[8];
float ins[4] = {10.4, 10.6, 120, 100000};
int main()
{
__m128 x = _mm_load_ps(ins); // Load the floats
__m128i y = _mm_cvtps_epi32(x); // Convert them to 32-bit ints
y = _mm_packus_epi32(y, y); // Pack down to 16 bits
y = _mm_packus_epi16(y, y); // Pack down to 8 bits
*(int*)out = _mm_cvtsi128_si32(y); // Store the lower 32 bits
printf("%d\n", out[0]);
printf("%d\n", out[1]);
printf("%d\n", out[2]);
printf("%d\n", out[3]);
return 0;
}
没有直接从float到byte的转换,_mm_cvtps_pi8
是复合。 _mm_cvtps_pi16
也是一个组合,在这种情况下,它只是在做一些你用洗牌撤销的无意义的事情。他们也 return 烦人 __m64
。
无论如何,我们可以转换成dwords(有符号,但没关系),然后打包(无符号)或打乱成字节。 _mm_shuffle_(e)pi8
生成一个 pshufb
,Core2 45nm 和 AMD 处理器不太喜欢它,你必须从某个地方获得掩码。
无论哪种方式,您都不必先四舍五入到最接近的整数,转换程序会这样做。至少,如果你没有弄乱舍入模式。
使用包 1:(未测试)——可能没有用,packusdw
已经输出未签名的字,但 packuswb
再次需要签名的字。保留下来是因为它在别处被引用。
cvtps2dq xmm0, xmm0
packusdw xmm0, xmm0 ; unsafe: saturates to a different range than packuswb accepts
packuswb xmm0, xmm0
movd somewhere, xmm0
使用不同的随机播放:
cvtps2dq xmm0, xmm0
packssdw xmm0, xmm0 ; correct: signed saturation on first step to feed packuswb
packuswb xmm0, xmm0
movd somewhere, xmm0
使用随机播放:(未测试)
cvtps2dq xmm0, xmm0
pshufb xmm0, [shufmask]
movd somewhere, xmm0
shufmask: db 0, 4, 8, 12, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
我们可以通过带符号饱和的第一阶段打包来解决无符号钳制问题。 [0-255]
适合带符号的 16 位 int,因此该范围内的值将保持未限定。该范围之外的值将保持在它的同一侧。因此,signed16 -> unsigned8 步骤将正确地限制它们。
;; SSE2: good for arrays of inputs
cvtps2dq xmm0, [rsi] ; 4 floats
cvtps2dq xmm1, [rsi+16] ; 4 more floats
packssdw xmm0, xmm1 ; 8 int16_t
cvtps2dq xmm1, [rsi+32]
cvtps2dq xmm2, [rsi+48]
packssdw xmm1, xmm2 ; 8 more int16_t
; signed because that's how packuswb treats its input
packuswb xmm0, xmm1 ; 16 uint8_t
movdqa [rdi], xmm0
这只需要 SSE2,packusdw
不需要 SSE4.1。
我认为这就是 SSE2 只包含从双字到字的带符号包,但包括从字到字节的带符号和无符号包的原因。 packuswd
仅在您的最终目标是 uint16_t
时才有用,而不是进一步打包。 (从那时起,您需要在将其提供给进一步的包之前屏蔽掉符号位)。
如果您确实使用了 packusdw -> packuswb
,当第一步饱和到 uint16_t
> 0x7fff 时,您会得到虚假结果。 packuswb
会将其解释为负 int16_t
并将其饱和为 0。packssdw
会将此类输入饱和到 0x7fff
,最大值 int16_t
.
(如果您的 32 位输入总是 <= 0x7fff,您可以使用其中任何一个,但是 SSE4.1 packusdw
takes more instruction bytes than SSE2 packsswd
,并且永远不会 运行s 更快。)
如果您的源值不能为负,并且您只有一个包含 4 个浮点数的向量,数量不多,您可以使用 harold 的 pshufb
想法。如果不是,您需要将负值限制为零,而不是通过将低字节改组到位来解决 运行。
使用
;; SSE4.1, good for a single vector. Use the PACK version above for arrays
cvtps2dq xmm0, xmm0
pmaxsd xmm0, zeroed-register
pshufb xmm0, [mask]
movd [somewhere], xmm0
可能比使用两个 pack
指令更有效,因为 pmax
可以 运行 在端口 1 或 5(Intel Haswell)上。 cvtps2dq
仅是端口 1,pshufb
和 pack*
仅是端口 5。