SSE内在函数:将32位浮点数转换为UNSIGNED 8位整数

Tim*_*ler 5 x86 sse mmx

使用SSE内在函数,我得到了一个四个32位浮点数的向量,它被钳位到0-255范围并四舍五入到最接近的整数.我现在想把这四个写成字节.

有一个内部函数_mm_cvtps_pi8会将32位转换为8位有符号整数,但问题是任何超过127的值都会被钳位到127.我找不到任何会压缩无符号8位值的指令.

我有一种直觉,我可能想要做的是移动指令的一些组合_mm_cvtps_pi16_mm_shuffle_pi8后面的操作,以获得我关心的四个字节到内存中.这是最好的方法吗?我将看看我是否可以弄清楚如何编码shuffle控制掩码.

更新:以下似乎完全符合我的要求.有没有更好的办法?

#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;
}
Run Code Online (Sandbox Code Playgroud)

更新2:根据Harold的回答,这是一个更好的解决方案:

#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;
}
Run Code Online (Sandbox Code Playgroud)

har*_*old 8

没有从float到byte的直接转换,_mm_cvtps_pi8是一个复合._mm_cvtps_pi16也是一个复合,在这种情况下,它只是做一些无意义的东西,你通过随机播放撤消.他们也回归烦人__m64的.

无论如何,我们可以转换为dwords(已签名,但无关紧要),然后打包(unsigned)或将它们随机转换为字节._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
Run Code Online (Sandbox Code Playgroud)

使用不同的shuffle:

cvtps2dq xmm0, xmm0  
packssdw xmm0, xmm0     ; correct: signed saturation on first step to feed packuswb
packuswb xmm0, xmm0
movd somewhere, xmm0
Run Code Online (Sandbox Code Playgroud)

使用shuffle :(未测试)

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
Run Code Online (Sandbox Code Playgroud)

  • 我真的很喜欢你的包装解决方案。好的是四舍五入和夹紧自动发生。然而,有一个极端情况,尽管我认为它不会影响我:如果我第一次将 100000 放入其中一个浮点数中,它会被限制为 65535(我假设)。然而,第二次它被重新解释为有符号值 (-1),然后被 packuswb 钳位为零。有什么低成本的解决方法吗? (2认同)

Pet*_*des 5

我们可以通过带有有符号饱和度的包装的第一阶段来解决无符号钳位问题. [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
Run Code Online (Sandbox Code Playgroud)

这只需要SSE2,而不是SSE4.1 packusdw.

我假设这是SSE2仅包含从dword到word的signed pack的原因,但是从word到byte都有signed和unsigned pack. packuswd仅在您的最终目标是uint16_t,而不是进一步打包时才有用.(从那时起,你需要先屏蔽掉标志位,然后再将它送到另一个包装中).

如果您确实使用了packusdw -> packuswb,当第一步饱和到uint16_t> 0x7fff 时,您会得到伪造的结果. packuswb将其解释为负数int16_t并将其饱和为0. packssdw将这些输入饱和至0x7fff最大值int16_t.

(如果您的32位输入始终<= 0x7fff,则可以使用其中一个,但SSE4.1packusdwSSE2packsswd占用更多的指令字节,并且从不运行得更快.)


如果您的源值不能为负值,并且您只有一个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
Run Code Online (Sandbox Code Playgroud)

可能比使用两个pack指令稍微更有效,因为pmax可以在端口1或5(Intel Haswell)上运行. cvtps2dq只是端口1,pshufbpack*只有5是端口.