14

浮動小数点値を飽和せずに 16 ビットの符号なし整数に変換したい (代わりにラップアラウンド/オーバーフロー)。

#include <iostream>
#include <xmmintrin.h>

void satur_wrap()
{
    const float bigVal = 99000.f;
    const __m128 bigValVec = _mm_set1_ps(bigVal);

    const __m64 outVec64 =_mm_cvtps_pi16(bigValVec);

#if 0
    const __m128i outVec = _mm_movpi64_epi64(outVec64);
#else

    #if 1
        const __m128i outVec  = _mm_packs_epi32(_mm_cvttps_epi32(bigValVec), _mm_cvttps_epi32(bigValVec));
    #else
        const __m128i outVec  = _mm_cvttps_epi32(bigValVec);
    #endif

#endif

    uint16_t *outVals = NULL;
    posix_memalign((void **) &outVals, sizeof(__m128i), sizeof(__m128i));

    _mm_store_si128(reinterpret_cast<__m128i *>(outVals), outVec);

    for (int i = 0; i < sizeof(outVec) / sizeof(*outVals); i++)
    {
        std::cout << "outVals[" << i << "]: " << outVals[i] << std::endl;
    }

    std::cout << std::endl
        << "\tbigVal: " << bigVal << std::endl
        << "\t(unsigned short) bigVal: " << ((unsigned short) bigVal)  << std::endl
        << "\t((unsigned short)((int) bigVal)): " << ((unsigned short)((int) bigVal)) << std::endl
        << std::endl;
}

実行例:

$ ./row
outVals[0]: 32767
outVals[1]: 32767
outVals[2]: 32767
outVals[3]: 32767
outVals[4]: 32767
outVals[5]: 32767
outVals[6]: 32767
outVals[7]: 32767

        bigVal: 99000
        (unsigned short) bigVal: 65535
        ((unsigned short)((int) bigVal)): 33464

式は((unsigned short)((int) bigVal))思いどおりに機能します (ただし、おそらく UB ですよね?)。しかし、SSE と非常によく似たものを見つけることができません。float何かが欠けているに違いありませんが、4 つの 32 ビットを 4 つの 32ビットに変換するプリミティブが見つかりませんでしたint


編集: おっと、32 ビット整数 -> 16 ビット符号なし整数変換でラップアラウンドを使用するのは「通常」だと思いました。しかし、私はそれ以来、_mm_packs_epi32符号付き飽和を使用していることを知りました (そして、 はないよう_mm_packus_epi32です)。モードまたは別のプリミティブを設定する方法はあります_mm_packus_epi32か?

4

2 に答える 2

10

私はあなたがおそらく命令を探していると思いますCVTTPS2DQ、その本質はです_mm_cvttps_epi32。参照: http://msdn.microsoft.com/en-us/library/c8c5hx3b( v = vs.71).aspx#vcref_mm_cvttps_epi32


これは、2 x SSEフロートベクトルを取り、それらをラップアラウンド付きの単一のパックされた8x16ビット符号なしベクトルに変換する完全な実装です。

#include <stdio.h>
#include <tmmintrin.h>

__m128i vec_float_to_short(const __m128 v1, const __m128 v2)
{
    __m128i v1i = _mm_cvttps_epi32(v1);
    __m128i v2i = _mm_cvttps_epi32(v2);
    v1i = _mm_shuffle_epi8(v1i, _mm_setr_epi8(0, 1, 4, 5, 8, 9, 12, 13, 255, 255, 255, 255, 255, 255, 255, 255));
    v2i = _mm_shuffle_epi8(v2i, _mm_setr_epi8(255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 4, 5, 8, 9, 12, 13));
    return _mm_or_si128(v1i, v2i);
}

int main(void)
{
    __m128 v1 = _mm_setr_ps(0.0f, 1.0f, -1.0f, 32767.0f);
    __m128 v2 = _mm_setr_ps(-32768.0f, 32768.0f, 99999.0f, -99999.0f);
    __m128i v3 = vec_float_to_short(v1, v2);

    printf("v1 = %vf\n", v1);
    printf("v2 = %vf\n", v2);
    printf("v3 = %vhu\n", v3);

    return 0;
}

これはSSSE3、別名SSE3.5、別名MNIを必要とするPSHUFB( )を使用することに注意してください(を参照)。したがって、これは適度に最新のCPU(過去6年ほどのIntelからのもの)でのみ機能します。_mm_shuffle_epi8tmmintrin.h

$ gcc -Wall -mssse3 vec_float_to_short.c -o vec_float_to_short
$ ./vec_float_to_short 
v1 = 0.000000 1.000000 -1.000000 32767.000000
v2 = -32768.000000 32768.000000 99999.000000 -99999.000000
v3 = 0 1 65535 32767 32768 32768 34463 31073
$ 

gccのすべてのバージョンがSIMDベクトルのprintfv形式指定子をサポートしているわけではないことに注意してください(この例では、OS XでAppleのgccを使用しています)。

于 2012-08-25T19:17:42.750 に答える
6

32 ビット整数 -> 16 ビット符号なし整数の変換に関する質問の一部のみに回答しています。

ラップアラウンドが必要なため、32 ビット整数を含む各ダブルワードの下位ワードを取得するだけです。これらの 16 ビット整数は、未使用のデータの 16 ビット部分とインターリーブされるため、それらを連続した配列にパックすると便利な場合があります。これを行う最も簡単な方法は、_mm_shuffle_epi8組み込み (SSSE3) を使用することです。

プログラムの移植性を高め、SSE2 命令セットのみを必要とする場合は、値を_mm_packs_epi32でパックし、次のトリックでその飽和動作を無効にすることができます。

x = _mm_slli_epi32(x, 16);
y = _mm_slli_epi32(y, 16);

x = _mm_srai_epi32(x, 16);
y = _mm_srai_epi32(y, 16);

x = _mm_packs_epi32(x, y);

このトリックは、16 ビット値の符号拡張を実行するため機能します。これにより、符号付き飽和はノーオペレーションになります。

同じトリックは次の場合にも機能し_mm_packus_epi32ます:

x = _mm_and_si128(x, _mm_set1_epi32(65535));
y = _mm_and_si128(y, _mm_set1_epi32(65535));
x = _mm_packus_epi32(x, y);

このトリックは、16 ビット値のゼロ拡張を実行するため機能します。これにより、符号なしサチュレーションがノーオペレーションになります。ゼロ拡張を実行する方が簡単ですが、使用可能にするには SSE4.1 命令セットが必要_mm_packus_epi32です。

1 つの命令を使用して、8 つの 16 ビット整数をパックすることができます_mm_perm_epi8。しかし、これにはかなりまれな XOP 命令セットが必要です。


そして、ここに飽和変換に関するいくつかの言葉があります。

またはに変更すると、実際に_mm_packus_epi32は組み込みが使用可能になります。SSE4.1 拡張機能をサポートするには、CPU とコンパイラの両方が必要です。#include <xmmintrin.h>#include <smmintrin.h>#include <x86intrin.h>

SSE4.1 互換の CPU やコンパイラがない場合、またはプログラムの移植性を高めたい場合は、_mm_packus_epi32組み込み関数を次のようなコードに置き換えます。

__m128i m1 = _mm_cmpgt_epi32(x, _mm_set1_epi32(0));
__m128i m2 = _mm_cmpgt_epi32(x, _mm_set1_epi32(65535));
x = _mm_and_si128(x, m1);
x = _mm_or_si128(x, m2);
于 2012-08-28T11:23:08.637 に答える