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 を使う。
__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 × 8uc_hi= 16bit × 8
に変換される。
2. int16 と 16bit × 16bit の乗算(PMULLW)
__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 ベクトル積コード
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);
}