メインコンテンツへスキップ
#83009active

simd, simd/archsimd: portable and architecture-specific SIMD

新規提案

要約

AIによる要約であり、誤りを含む場合があります。

概要

GOEXPERIMENT=simd の下で試験提供されてきた SIMD(1命令で複数データをまとめて処理する機能)API のうち大きな部分を、実験フラグなしで使える正式機能にする提案です。対象は、ベクトル長に依存しない移植性重視の simd パッケージと、アーキテクチャ固有の simd/archsimd パッケージの2つです。

ステータス変更

(新規) → active
2026-11-11 の Proposal Review Meeting で初めて議論され、週次レビューの active 列に入りました。会議では次の点が話し合われました。

  • Equal の命名と == 演算子への対応
  • 型変換(reshape)API の設計
    あわせて、@cherrymui と @aclements が API を細かく確認済みであることも共有されました。SIMD プロジェクト外のレビュアーとしては @adonovan が加わります。Go 1.27 時点では AMD64 版の正式化(#78979)を見送りました。その後、ARM64 と WebAssembly への展開や実行可能仕様の整備によって、プラットフォーム間で API がまとまってきたという根拠がそろい、このタイミングでの提案になりました。

技術的背景

現状の問題点

  • Go 1.26 で AMD64 向けの simd/archsimd が GOEXPERIMENT=simd の下で導入されました(#73787)。
  • Go 1.27 では次の変更がありましたが、いずれもまだ実験扱いです。
    • AMD64 API の改良
    • ARM64 NEON と WebAssembly SIMD への拡張
    • ベクトル長に依存しない移植性重視の simd パッケージの追加(#78902)
  • 実験フラグが必要なので、ライブラリ作者が本番コードで SIMD を使いにくい状態が続いています。

提案された解決策

  • 正式化の範囲: 純 Go で書かれた実行可能仕様(#81819)がカバーする操作だけを正式化します。対象は AMD64(AVX/AVX2/AVX512)、ARM64(NEON)、WebAssembly の3つです。その他のプラットフォームでは simd がソフトウェア実装(フォールバック)で動きます。
  • 「1つの名前は1つの意味」ルール: 同じ名前の操作は、どのプラットフォームでも、どちらのパッケージでも、同じシグネチャと同じ意味を持たなければなりません。ある名前がすべての環境に存在する必要はありません。また、1つのメソッドはカテゴリ(ベクトルとマスクの2つだけ)内のすべての型で同じ意味でなければなりません。
  • 仕様の役割: 実行可能仕様は各操作の厳密な意味を定めます。さらに次の用途にも使われます。
    • 適合性テスト(これにより QEMU のバグも見つかりました。実機ではすべてパスしています)
    • 全操作で統一されたドキュメントの生成元
  • SVE の扱い: ARM64 SVE(ハードウェア側でベクトル長が可変の命令セット)は今回の正式化の対象外です。ただしプロトタイプ(#79781)があり、API がベクトル長可変のハードウェアにも適用できることを示しています。

これによって何ができるようになるか

  • 画像処理、圧縮、ハッシュ、JSON パーサー、数値計算などのライブラリで、アセンブリを書かずに Go コードのまま SIMD を使えるようになります。
  • simd パッケージを使えば、1つのコードが AMD64・ARM64・Wasm で動き、それ以外の環境でもフォールバックで正しく動きます。
  • 特定の命令(AVX512 など)を細かく使いたい場合は、archsimd で性能をとことん追求できます。
  • 実験フラグが不要になるので、外部に公開するモジュールでも安心して使えるようになります。

コード例

以下は、issue 内の未解決論点(ベクトルとマスクの等価判定)を、issue に書かれた選択肢に沿って示したものです。

// Before / 現状(選択肢1): Equal は要素ごとの比較で Mask を返す
m := x.Equal(y)          // 各レーンの比較結果を持つ Mask
allEq := x.Equal(y).All() // ベクトル全体が等しいか
masksEq := a.Xor(b).Not().All() // マスク同士の等価判定は回りくどい
// After 案(選択肢2): 要素ごとの Equal はそのまま、== / != を許可
if x == y { /* 全要素が等しい */ }
if a != b { /* マスクが異なる */ }
// After 案(選択肢3): 要素ごとの比較を Equals に改名し、Equal は bool を返す
m := x.Equals(y)  // Mask
ok := x.Equal(y)  // bool

議論のハイライト

  • Equal の命名: 現在の Vec.Equal(Vec) Mask は、「Equal は bool を返す」という Go の慣例から外れています。会議では次の理由から Equal 以外の名前は不自然だという意見が出ました。
    • 要素ごとの演算でも Add を AddElems とは呼んでいない
    • 改名するなら NotEqual や GreaterEqual なども改名が必要になる
  • == 演算子: Go は構造体や配列に == を許しており、ベクトルも本質的には構造体や配列と同じだと見なせます。そのため会議では == を許可することに全員が前向きでした。選択肢2が有力と考えられます。
  • 外部ライブラリとの関係: コミュニティからは、go-cmp のように Equal() bool メソッドを探して等価判定に使うライブラリとの関係が指摘されました。この指摘では、Mask は概念的には bool のベクトルなので Equal が Mask を返すのは自然だと述べられています。
  • 型変換(reshape)API: 現在は ToBits() → ReshapeTo... → BitsTo... の3段階が必要で、使いにくいです。代替案として、型を持たない Bits128/256/512(およびベクトル長可変の Bits)を導入する案が示されています。会議では「意味のある型変換は uint 同士くらいでは」という疑問が出ましたが、実際には意外なほど多様な型変換が必要とされているとのことです。
  • 後方互換への配慮: 選択肢3を採る場合は、段階的に移行することで、実験版を使う既存コードを無用に壊さないようにします。具体的には、まず //go:fix 付きで旧 Equal を非推奨にし、次のリリースで置き換えます。

関連リンク