コンテンツにスキップ

最終更新:2026-10-12

C++ SIMD ベクトル化

最近、ベクトル化関連の内容を見ていて、少し頭が痛くなったので、この機会に高性能SIMDプログラミングを学んでいきます。

SIMDはsingle-instruction multiple-dataの略称で、単一命令複数データを意味します。

従来のコンピュータアーキテクチャでは、CPUは一度に1つのデータ要素しか処理できません。しかし、多くのタスクは大量のデータに対して同じ操作を実行する必要があります。例えば、配列内のすべての要素に対する加算、乗算、論理操作などです。SIMDプログラミングは、CPUに専用の命令セットを提供することで、CPUが複数のデータ要素に対して同時に同じ操作を実行できるようにします。

この処理方式は、ベクトル、行列、画像、オーディオ、ビデオなどのデータが絡む計算に特に適しています。

現在使用されることが多いのはSSE、SSE2、AVX128、AVX256、AVX512です。

本節では、AVX512のいくつかの操作を簡単に学びます。操作の種類は非常に多いので、ここでは一部を紹介するにとどまります。

1.用語

まず最初の問題は、simdプログラミングのコードが普段書いているコードと見た目がだいぶ異なり、様々なアンダースコアや命名があり、全く読めないということです。どのように理解すればよいのでしょうか?

例えば:

  • _mm512_set1_ps

Broadcast single-precision (32-bit) floating-point value a to all elements of dst.

  • _mm512_set1_epi32

Broadcast 32-bit integer a to all elements of dst.

そこで、次の表を見つけました:

Abbreviation Full Name C/C++ Equivalent
ps packed single-precision float
ph packed half-precision None*
pd packed double-precision double
pch packed half-precision complex None*
pi8 packed 8-bit integer int8_t
pi16 packed 16-bit integer int16_t
pi32 packed 32-bit integer int32_t
epi8 extended packed 8-bit integer int8_t
epi16 extended packed 16-bit integer int16_t
epi32 extended packed 32-bit integer int32_t
epi64 extended packed 64-bit integer int64_t
epi64x extended packed 64-bit integer int64_t

https://stackoverflow.com/questions/70911872/what-are-the-names-and-meanings-of-the-intrinsic-vector-element-types-like-epi6

さらに例を挙げます:

_mm512_mask_load_ps

_mm512_mask_loadu_ps

uはunorderedを表し、順序なしのロードを意味します。_mm512_mask_loadu_ps関数を使用してメモリ上のデータをロードするとき、メモリアドレスに対するアライメント要件は一切課されません。一方、_mm512_mask_load_psは64バイトアライメントの要件を満たす必要があります。

このように対応表と突き合わせながら学ぶと、それぞれのインターフェースの意味を非常に速く知ることができます。

関連するAPIはIntel Intrinsics Guideで確認できます。

https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html

2.実際の例

2.1 最小値を求める

512ビットには、32ビットfloatを16個格納できます。

static inline rf_512 load(float* ptr) { return _mm512_loadu_ps(ptr); }
float a[width] = {1.0, 2.0,  3.0,  22.0, 5.0,  17.0, 9.0,  8.0,
                  9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
float b[width] = {3.3, 6.2,   5.3,   4.4,   5.5,   6.6,   7.7,  8.8,
                  9.9, 10.10, 21.11, 12.12, 13.13, 14.14, 15.0, 16.16};
rf_512 data = minimum(load(a), load(b));

すると、次の結果をすぐに得ることができます:

1 2 3 4.4 5 6.6 7.7 8 9 10 11 12 13 14 15 16

2.2 データの並び順を素早くシャッフルする

入力データが

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

であるとき、次の結果を素早く得ることができます:

16.16 2 14.14 13.13 5 6 7 8 9 10 11 12 13 14 15 16

対応する実装:

static inline rf_512 permutexvar(ri_512 idx, rf_512 src) {
  return _mm512_permutexvar_ps(idx, src);
}
/*
raw data: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
shuffle data: 16.16 2 14.14 13.13 5 6 7 8 9 10 11 12 13 14 15 16
*/
void print_permutexvar_mask() {
  float a[width] = {1.0, 2.0,  3.0,  4.0,  5.0,  6.0,  7.0,  8.0,
                    9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
  float b[width] = {1.1, 2.2,   3.3,   4.4,   5.5,   6.6,   7.7,  8.8,
                    9.9, 10.10, 11.11, 12.12, 13.13, 14.14, 15.0, 16.16};
  mask_type mask = make_bit_mask<1, 0, 1, 1, 0>();
  int idx_array[width] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
  ri_512 idx = _mm512_loadu_si512((__m512i*)idx_array);
  rf_512 result = permutexvar_mask(load(a), mask, idx, load(b));
  float result_arr[width];
  store(result, result_arr);
  std::cout << "raw data: ";
  for (int i = 0; i < width; i++) {
    std::cout << a[i] << " ";
  }
  std::cout << std::endl;

  std::cout << "shuffle data: ";
  for (int i = 0; i < width; i++) {
    std::cout << result_arr[i] << " ";
  }
  std::cout << std::endl;
}

2.3 ローテーション

ある配列:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

に対して、ローテーションして次の結果を得ることができます:

4 5 6 7 8 9 10 11 12 13 14 15 16 1 2 3

または

14 15 16 1 2 3 4 5 6 7 8 9 10 11 12 13

つまり、rotate downまたはrotate upです。

ここでは、次のように実装できます:

rf_512 rotateGeneral(float* arr, int s) {
  int idx_array[width];
  for (int i = 0; i < width; i++) {
    idx_array[i] = (i + s) % width;
  }
  ri_512 idx = _mm512_loadu_si512((__m512i*)idx_array);
  rf_512 result = permutexvar(idx, load(arr));
  return result;
}

その他にも例はあり、simdを使用することで、非常に興味深いアルゴリズムを実現でき、配列やバッチデータの処理を高速化できることがわかります。

これからもsimdを学び続けていきます。一緒に頑張りましょう〜

注:本節の完全なサンプルは知識星球で更新済みです。よろしくお願いします〜

関連トピック

コメント