SIMDとベクトル演算の原理(SSE/AVX/NEON/SVE)
なぜ1命令で複数データを一気に処理できるのか。SSE/AVXの固定長とSVEのスケーラブル方式、自動ベクトル化と整列の勘所を原理から押さえ、CPUの並列性能を引き出せます。
- 1.SIMDは1つの命令を幅広いベクトルレジスタ内の複数要素へ一斉適用するデータレベル並列で、要素数ぶんスループットを稼ぐ。
- 2.SSE/AVXはレジスタ幅を命令ごとに固定する方式、ARMのSVEは幅をハードウェア任せにするスケーラブル方式で、後者は同じバイナリが幅違いの実装で動く。
- 3.効果はループの自動ベクトル化と整列・連続アクセスに依存し、依存・分岐・端数処理が並列化を阻む。
データレベル並列という考え方
通常のスカラ命令は add 1回で1組の足し算を行います。しかし画像のピクセル処理や行列演算では、同じ操作を大量の要素へ繰り返すだけのことが多い。ここで効くのが SIMD(Single Instruction, Multiple Data) です。幅の広いベクトルレジスタに複数の要素を詰め込み、1つの命令を全要素へ一斉に適用します。
スカラ: add r0, r1, r2 ← 1要素ぶんの加算
SIMD : vaddps ymm0, ymm1, ymm2 ← 256bitに詰めた8個のfloatを同時に加算
ymm1: [a0 a1 a2 a3 a4 a5 a6 a7]
ymm2: [b0 b1 b2 b3 b4 b5 b6 b7]
ymm0: [a0+b0 ... a7+b7] ← 1命令で8要素
これは命令を並べ替えて並列度を稼ぐアウトオブオーダ実行(命令レベル並列)とは別の軸で、データレベル並列(DLP) と呼びます。命令フェッチ・デコードは1回で済むため、同じ電力でより多くの演算をこなせるのが本質的な利点です。1命令あたりの制御コストを要素数で割り勘にする、という発想はGPUのSIMTとも共通します。
レジスタ幅と要素数
SIMD の性能はベクトルレジスタの幅で決まります。幅が広いほど1命令で扱える要素が増えます。要素数は単純な割り算です。
1命令あたりの要素数 = レジスタ幅(bit) / 要素のビット幅
例: 256bit幅でfloat(32bit) → 8要素 / double(64bit) → 4要素
同じレジスタを、要素のサイズを変えて「8bit×多数」「32bit×中数」のように見立てられるのもポイントです。x86 系の拡張は、この幅を世代ごとに倍々に広げてきました。
| 拡張 | アーキテクチャ | レジスタ幅 | float要素数/命令 |
|---|---|---|---|
| SSE | x86 | 128bit | 4 |
| AVX / AVX2 | x86 | 256bit | 8 |
| AVX-512 | x86 | 512bit | 16 |
| NEON | ARM (AArch64) | 128bit | 4 |
| SVE | ARM | 128〜2048bit可変 | 実装依存 |
幅が倍になれば理論スループットも倍ですが、後述するように整列やメモリ帯域が伴わなければ実効値は伸びません。
固定長(AVX)とスケーラブル(SVE)
ここが上級者の分かれ目です。SSE/AVX/NEON はいずれも固定長方式で、レジスタ幅が ISA に焼き込まれています。vaddps ymm という命令は「256bit を処理する」と決め打ちで、幅が変われば別の命令(別のバイナリ)が必要です。512bit を使いたければ AVX-512 向けに再コンパイルし直すことになります。
ARM の SVE(Scalable Vector Extension) はこの前提を覆します。命令はレジスタ幅を規定しません。プログラムは「ベクトル長は実行時に決まる未知の値」として書かれ、実際の幅はハードウェア実装が 128〜2048bit の範囲で選びます。同じ実行バイナリが、幅の異なる実装でそのまま動くのが最大の特徴です。
これを支えるのが2つの仕組みです。
- ベクトル長アグノスティック(VLA):コードは幅を定数で持たず、実行時に問い合わせる。ループは「処理済み要素を毎回ベクトル長ぶん進める」形で書く。
- 述語(predicate)レジスタ:どのレーンを有効にするかをビットマスクで持ち、端数や条件付き処理を分岐なしに扱う。
SVEのループ骨格(概念)
i = 0
while (i < N):
pred = whilelt(i, N) # i+レーン番号 < N の要素だけ有効化
v = load(a + i, pred) # 有効レーンのみロード
store(b + i, v*2, pred)
i += vector_length() # 幅は実行時に決まる
固定長では、新世代で幅が広がるたびに命令セットを追加し、ソフトを再コンパイル・分岐対応する必要があります。SVE は幅を抽象化することで「一度書けば、より広い未来のハードでも速くなる」を狙います。ベクトル長は実行時に判明するので、端数処理を述語に任せ、幅違いを意識せずに済みます。後継のSVE2やRISC-Vのベクトル拡張(RVV)も同じスケーラブル思想を採ります。
自動ベクトル化と整列制約
SIMD 命令を手で書く(イントリンシック)こともできますが、実務の多くは自動ベクトル化、すなわちコンパイラがスカラのループをSIMD命令へ変換する機能に頼ります。コンパイラは反復間に依存がないこと(i番目の結果がi-1番目に依存しない)を確認し、ループを「複数反復を1命令で束ねる」形に書き換えます。これを阻む典型要因が次です。
| 阻害要因 | なぜ並列化できないか | 対処の方向 |
|---|---|---|
| 反復間の依存(RAW) | 前の結果を次が使うと束ねられない | アルゴリズム変更・リダクション化 |
| ポインタエイリアス | 別ポインタが重なる可能性を排除できない | restrict修飾で非重複を明示 |
| 制御フロー(分岐) | レーンごとに行き先が割れる | 述語・マスク・条件移動に置換 |
| 関数呼び出し | 副作用が読めず束ねられない | インライン化・ベクトル版関数 |
もう1つの鍵が整列(アライメント) です。多くのSIMDロード/ストアは、メモリ上で要素列がレジスタ幅の境界に揃っていると最も効率よく動きます。256bit アクセスなら 32 バイト境界です。非整列アクセスはキャッシュラインをまたいで2回に分割されることがあり、これが実効帯域を削ります。本質的には固定幅のキャッシュライン/バースト単位でメモリが動くためで、境界をまたぐと余計な転送が生じます。
近年のCPUは非整列ロードのペナルティを縮めましたが、ゼロではありません。とくに array[i] を飛び飛び(大ストライド)に読むと、必要な要素が連続して並ばず、SIMDレジスタへ詰め直すギャザー処理が要り、連続ロードより大幅に遅くなります。データを構造体配列(AoS)ではなく配列構造体(SoA)に並べ替え、同種の値を連続させるのが定石です。
端数・分岐・水平演算という弱点
SIMD はまっすぐな大量データに強い一方、いくつかの構造的弱点があります。第一に端数(テール)処理。要素数がベクトル幅で割り切れないと、最後の余りをスカラで後始末する必要があり、固定長方式ではこのコードが必ず付きまといます。SVE の述語は、まさにこの端数を分岐なしで畳むためにあります。
第二に分岐。レーンごとに条件が割れると1命令で両方を実行できないため、両経路を計算してマスクで選ぶ(述語実行)形になり、無駄な計算が増えます。第三に水平演算。総和のようにレーン間をまたぐ集約(horizontal add)はレーン独立というSIMDの前提に反し、専用命令や段階的な縮約が要って割高です。
配列の総和は素直に書くと「accに足し込む」逐次依存になり、ベクトル化できません。そこで部分和ベクトルを複数本持ち、最後にレーン間を縮約します。浮動小数点では加算順序が変わるため、結果がビット単位で逐次版と一致しない点に注意が必要です(結合則が厳密には成り立たない)。コンパイラが自動でこれをやるには高速演算オプションの許可が要ることがあります。
「SIMDは1命令を複数データへ適用するデータレベル並列」「要素数=レジスタ幅÷要素ビット幅」「SSE/AVX/NEONは固定長、SVE/RVVはベクトル長を実行時に決めるスケーラブル方式で同一バイナリが幅違いで動く」「自動ベクトル化は反復間依存・エイリアス・分岐に阻まれ、整列と連続アクセスが性能を左右する」の4点が頻出です。SIMD(1スレッドで複数データ)とSIMT(複数スレッドで1命令)の区別も問われます。
まとめ
- SIMD は幅広いベクトルレジスタに詰めた複数要素へ1命令を一斉適用するデータレベル並列で、制御コストを要素数で割り勘にしてスループットを稼ぐ。
- 1命令あたりの要素数はレジスタ幅÷要素ビット幅で決まり、SSE(128)→AVX(256)→AVX-512(512)と幅を広げてきた。
- 固定長(SSE/AVX/NEON)は幅をISAに焼き込むため再コンパイルが要る。SVEは幅を抽象化し、述語とベクトル長アグノスティックな書き方で同一バイナリが幅違いの実装で動く。
- 実効性能は自動ベクトル化の成否と整列・連続アクセスに依存し、反復間依存・分岐・端数・水平演算が並列化を阻む。
SIMD はISAの設計思想の上に積み上がる拡張です。CPU内のデータ並列を引き出す鍵は、突き詰めれば「依存を断ち、揃えて連続に並べ、端数を述語で畳む」の3点に集約されます。
CPU/メモリ/ディスク Article
SIMDとベクトル演算の原理(SSE/AVX/NEON/SVE)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
SIMD
比較で見る軸
難易度: advanced / カテゴリ: CPU/メモリ/ディスク / タグ数: 5
導入後に効く点
SSE/AVXはレジスタ幅を命令ごとに固定する方式、ARMのSVEは幅をハードウェア任せにするスケーラブル方式で、後者は同じバイナリが幅違いの実装で動く。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- CPU/メモリ/ディスク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「SIMD / AVX」に近いか確認する。
- 強みである「SIMDは1つの命令を幅広いベクトルレジスタ内の複数要素へ一斉適用するデータレベル並列で、要素数ぶんスループットを稼ぐ。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。