7

SSE 組み込み関数を使用して、0 から 255 の範囲にクランプされ、最も近い整数に丸められた 4 つの 32 ビット浮動小数点数のベクトルを取得しました。これらの 4 つをバイトとして書き出したいと思います。

_mm_cvtps_pi832 ビットを 8 ビットのsigned int に変換する組み込み関数がありますが、127 を超える値はすべて 127 にクランプされるという問題があります。符号なし 8 ビット値にクランプする命令が見つかりません。

私がやりたいことは、_mm_cvtps_pi16とそれに_mm_shuffle_pi8続く move 命令を組み合わせて、気になる 4 バイトをメモリに入れることだという直感があります。それが最善の方法ですか?シャッフル コントロール マスクをエンコードする方法を見つけられるかどうかを確認します。

更新: 以下は、私が望むことを正確に行うようです。より良い方法はありますか?

#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;
}

UPDATE2: ハロルドの回答に基づいたさらに優れたソリューションを次に示します。

#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;
}
4

2 に答える 2

7

符号付き飽和でパッキングの最初の段階を実行することで、符号なしクランプの問題を解決できます。 [0-255]符号付き 16 ビット整数に収まるため、その範囲の値はクランプされないままになります。その範囲外の値は、同じ側にとどまります。したがって、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

これには、SSE4.1 ではなく、SSE2 のみが必要ですpackusdw

これが、SSE2にdwordからwordへの署名付きパックのみが含まれていた理由であると思いますが、ワードからバイトへの署名付きパックと未署名パックの両方が含まれていました。 は、さらにパッキングするのではなく、packuswd最終目標が である場合にのみ役立ちます。uint16_t(それ以降、次のパックにフィードする前に、符号ビットをマスクする必要があります)。

を使用しpackusdw -> packuswbた場合、最初のステップがuint16_t> 0x7fff に飽和したときに偽の結果が得られます。 packuswbはそれを負として解釈し、int16_tそれを 0 packssdwに飽和させます。そのような入力を0x7fff最大に飽和させint16_tます。

(32 ビット入力が常に <= 0x7fff の場合は、どちらも使用できますが、SSE4.1はpackusdwSSE2packsswdよりも多くの命令バイトを必要とし、実行速度は決して速くありません。)


ソース値を負にすることができず、4 つの float のベクトルが 1 つしかない場合は、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

ポート 1 または 5 (Intel Haswell) で実行できる packため、2 つの命令を使用するよりもわずかに効率的です。はポート 1 のみ、はポート 5 のみです。pmaxcvtps2dqpshufbpack*

于 2015-12-03T21:44:43.873 に答える