2026年10月26日月曜日

unsigned char(8bit)× int16(16bit)のベクトル積

 unsigned charとint16のベクトル積のSSEコード

結論から言うと、unsigned char(8bit)× int16(16bit)のベクトル積を SSE2 で行うには、 「8bit → 16bit へのゼロ拡張」+「16bit × 16bit の乗算」 という 2 ステップ構成になります。

SSE2 には 8bit × 16bit の直接乗算命令は存在しないため、 unpack(ゼロ拡張)→ PMULLW(16bit multiply) が正解です。

最速の SSE2 コードを完成形で提示します。

🧩 unsigned char × int16 の SSE2 ベクトル積の正しい流れ

1. unsigned char(8bit)を 16bit にゼロ拡張

SSE2 の PUNPCKLBW / PUNPCKHBW を使う。

cpp
__m128i bytes = _mm_loadu_si128((__m128i*)uc);   // unsigned char × 16
__m128i zero  = _mm_setzero_si128();

// 下位8バイト → 16bit に拡張
__m128i uc_lo = _mm_unpacklo_epi8(bytes, zero);

// 上位8バイト → 16bit に拡張
__m128i uc_hi = _mm_unpackhi_epi8(bytes, zero);

これで:

  • uc_lo = 16bit × 8

  • uc_hi = 16bit × 8

に変換される。

2. int16 と 16bit × 16bit の乗算(PMULLW)

cpp
__m128i s16_lo = _mm_loadu_si128((__m128i*)i16);      // int16 × 8
__m128i s16_hi = _mm_loadu_si128((__m128i*)(i16 + 8)); // int16 × 8

// 16bit × 16bit の乗算(下位16bit)
__m128i prod_lo = _mm_mullo_epi16(uc_lo, s16_lo);
__m128i prod_hi = _mm_mullo_epi16(uc_hi, s16_hi);

_mm_mullo_epi16(PMULLW)は:

  • signed 16bit × signed 16bit → 下位 16bit を返す

unsigned char はゼロ拡張しているので signed として扱っても問題なし。

✔ 完成形:unsigned char × int16 の SSE2 ベクトル積コード

cpp
void mul_uc_i16_sse(const unsigned char* uc, const int16_t* s16,
                    int16_t* out)
{
    __m128i bytes = _mm_loadu_si128((__m128i*)uc);
    __m128i zero  = _mm_setzero_si128();

    // 8bit → 16bit ゼロ拡張
    __m128i uc_lo = _mm_unpacklo_epi8(bytes, zero);
    __m128i uc_hi = _mm_unpackhi_epi8(bytes, zero);

    // int16 をロード
    __m128i s16_lo = _mm_loadu_si128((__m128i*)s16);
    __m128i s16_hi = _mm_loadu_si128((__m128i*)(s16 + 8));

    // 16bit × 16bit の乗算
    __m128i prod_lo = _mm_mullo_epi16(uc_lo, s16_lo);
    __m128i prod_hi = _mm_mullo_epi16(uc_hi, s16_hi);

    // 結果をストア
    _mm_storeu_si128((__m128i*)out,     prod_lo);
    _mm_storeu_si128((__m128i*)(out+8), prod_hi);
}

0 件のコメント:

コメントを投稿