
2026/09/25 20:47
Go のプラットフォーム非依存 SIMD
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Go 1.26 および 1.27 は実験的な SIMD API を導入し、独自の組立コードなしに Go の内部で直接高速なベクトル化計算を可能にする画期的な一歩です。以前は SIMD にアクセスするには
archsimd パッケージのようなプラットフォーム固有の組立が必要であり、利用範囲はニッチなカーネル開発者に限定されていました。新しい移植可能な simd パッケージにより、この能力が多種多様なアーキテクチャで統合されています。Go 1.26 では amd64 が対応し、1.27 では arm64 (NEON) および wasm のサポートが追加されました。これらのアーキテクチャはベクトルサイズ(wasm/PowerPC/s390x は固定の 128 ビットに対し、amd64 は最大 512 ビット)、マスク戦略、命令セット(AVX, AVX2, AVX512, NEON, SVE)において著しく異なります。
このパッケージは C++ の Highway ライブラリを loosely に基づいたプラットフォーム非依存のインターフェースを提供し、同じ命令セットをサポートします。「書けばよい」コードにより、能力のあるプラットフォームではほぼ組立レベルの速度、あるいは他では適切なエミュレーションによって動作させます。この実験的な API を使用するには、ビルド時に
GOEXPERIMENT=simd を設定します(archsimd と同様)。挙動に対する制御も GODEBUG 設定を通じて可能で、完全なエミュレーション (simd=0) から特定のベクトル幅 (simd=128, 256, 512)、そして許可モードまでの範囲が利用できます。
API は大文字の複数形プリミティブ(例:
simd.Uint8s, simd.Float32s)を使用し、.Load(), .MulAdd(), .Store() などの演算を提供します。比較演算は特定のマスク型(例:Int8s 用の Mask8s)を生成し、これらは選択とフィルタリングに使用されます。パッケージには Load/ブロードキャスト、ストア/文字列、算術、論理/マスク、比較、変換、マスクメソッド、シフト/回転、再整形演算を含む包括的な API が含まれています。開発者は ToArch() および FromArch 関数を使用してアーキテクチャ固有のコードと切り替えができ、プラットフォーム固有の実装とフォールバックエミュレーションを組み合わせることができます。
現在のバージョンにはベクトル要素の合計のような特定のリダクション手法が不足しており(次のリリースでは
simd.ReduceSum として計画されている)、OnesCount() などの欠落する機能はプラットフォームごとに実装でき、未対応ハードウェアに対して共有のエミュレーションフォールバックを備えることができます。最終的に、このアップデートは SIMD を専門的な制約から、多様なハードウェア環境で高性能計算が必要なすべてのユーザーに利用可能な汎用ツールへと変革します。本文
Go 1.26/1.27 SIM API: 単命令複数データ (SIMD) 操作の実験的 API
Go 1.26 および Go 1.27 では、単命令複数データ(SIMD)操作向けの実験的な APIが導入されました。現代の CPU に標準装備された SIMD機能を活用することで、暗号処理やデータ処理、AI など多数の計算負荷の高いタスクが大幅に高速化されます。
背景と動機
従来の課題
- 手動のアセンブリ記述: SIMD 機能へのアクセスは以前はGo アセンブリの手書きのみでした。
- リソースの非効率: パフォーマンスクリティカルな計算カーネル以外では、CPU の一部が未使用のままとなっていました。
アーキテクチャ間の多様な差異
SIMD はプラットフォーム間で以下の点で大きな差異があります:
- ベクトルサイズ:
- 固定サイズのみ(wasm, PowerPC, s390x: 128 ビット)。
- 複数の固定サイズ(amd64: 128/256/512 ビット)。
- 未指定サイズ(Riscv64: 128〜65536 ビット、2 のべき乗のみ)。
- 固定・可変の併存(arm64: NEON 固定 128 ビット / SVE 可変 128〜2048 ビット)。
- マスク処理:
- マスクなし(wasm, AVX, NEON など)。
- 特殊なマスクレジスタ(AVX512, RVV)。
- SVE や AVX2 のようなベクトルベースのマスク制御。
- 演算サポート: リバールや暗号関連操作、比較演算など、アーキテクチャごとに指令が異なります。
これに対応するため、
archsimd パッケージで一貫性を保とうとしましたが、マルチプラットフォームでの設計・テストが困難であり、限界がありました。
simd
パッケージの概要
simd新しい
simd パッケージは、これらの差異を隠蔽し、以下の目標を実現します:
- 汎用性: 特定のベクトルサイズに依存せず、多くのベクトル化実装に利益をもたらす。
- 高効率性: ハードウェアと一致する場合はアセンブリ言語と同程度の効率。
- 互換性: サポートされていない機能については効率的なエミュレーションを提供。
- 可読性: LLM や人間がコードを書く際にも読みやすい設計。
使用条件:
を使えないプラットフォームや、機能不足の場合は全演算がエミュレートされます。archsimd- ビルド時に以下を設定して実験的に利用します:
GOEXPERIMENT=simd
型と基本用法
デフォルトの API(共通部分)
simd ベクトル型は大文字の複数形プリミティブ型です(例:simd.Uint8s, simd.Float32s)。スライスからロードし、結果をスライスにストアします。
// innerProduct は x と y の内積を返します。 func innerProduct(x, y []float32) float32 { var a simd.Float32s var i int // 要素単位ではなくブロック単位で処理 for i = 0; i < len(x)-a.Len()+1; i += a.Len() { u := simd.LoadFloat32s(x[i : i+a.Len()]) v := simd.LoadFloat32s(y[i : i+a.Len()]) a = u.MulAdd(v, a) } // 残りの要素を処理(部分ロードが必要) if i < len(x) { u, _ := simd.LoadFloat32sPart(x[i:]) v, _ := simd.LoadFloat32sPart(y[i:]) a = u.MulAdd(v, a) } return sum(a) } // sum は x の要素のスカラー合計を返します。 func sum(x simd.Float32s) float32 { s := make([]float32, x.Len()) x.Store(s) // スカラーへ展開して合計 var r float32 for _, e := range s { r += e } return r }
注意:
は現在のsumパッケージではサポートされていませんが、次期リリースでsimdが追加される予定です。ReduceSum
マスクによるフィルタリング
SIMD 比較は対応するベクトル要素幅に固有のマスク値を生成します(例:
Int8s 比較は Mask8s)。これを用いてベクトルを選択・フィルタリングできます。
Go 1.27 時点のサポートされている操作
Go 1.27 でサポートされる主な操作カテゴリです。(Y: サポートあり、空: サポートなし)
パッケージレベルのロード/ブロードキャスト
| 関数 | 全型対応 |
|---|---|
| Y |
| Y |
| Y |
ストア/文字列化
| 操作 | 全型対応 |
|---|---|
| Y |
| Y |
| Y |
主要な算術・論理演算
- 基本演算:
,Add
,Sub
,Mul
,Div
など(全型対応)。Neg - 比較演算:
,Equal
,Greater
などはすべてマスク値を返します。Less - ビット操作:
,And
,Or
,Xor
など。Not - 特殊機能:
(符号なし整数のみ),Abs
(浮動小数点のみ),Sqrt
(条件分岐)。IfElse
| 関数 | Int8s | Int16s | Int32s | Int64s | Uint8s | ... | Float32s | Float64s |
|---|---|---|---|---|---|---|---|---|
| Add | Y | Y | Y | Y | Y | Y | Y | Y |
| Mul | Y | Y | Y | Y | Y | Y | Y | Y |
| Sub | Y | Y | Y | Y | Y | Y | Y | Y |
| Max/Min | Y | Y | Y | Y | Y | Y | Y | Y |
(※詳細な演算子対応表は元のドキュメントを参照してください。一部の型(例:
の乗算など)ではサポートされない場合があります)Int64s
プラットフォーム固有コードとの移行
simd パッケージが提供する機能に不足がある場合、または特定の機能をエミュレーションしたくない場合は、アーキテクチャ固有の archsimd へ切り替え可能です。
型変換パターン
- プラットフォーム特定型へ:
を使用して、プラットフォーム固有のベクトル型を取得します。v.ToArch() - 共通型へ:
を使用して、プラットフォーム固有型を共通型に戻します。simd.<SimdType>FromArch()
実装例:OnesCount
メソッドの実装
OnesCount各プラットフォームで異なる指令(またはエミュレーション)を使用して、1 ビットの数をカウントする機能を実装します。
1. amd64 (AVX512 サポートあり)
高性能な命令列を使用します。
//go:build goexperiment.simd && amd64 package simd_test import ( "simd" "simd/archsimd" ) func OnesCount(v simd.Int8s) simd.Int8s { switch x := v.ToArch().(type) { case archsimd.Int8x16: lut := archsimd.LoadInt8x16Array(&popcnt4x16) mask0f := archsimd.BroadcastInt8x16(0x0f) // ... (詳細な命令列処理) return simd.Int8sFromArch(...) case archsimd.Int8x32: lut := archsimd.LoadInt8x32Array(&popcnt4x32) // ... return simd.Int8sFromArch(...) case archsimd.Int8x64: return simd.Int8sFromArch(x.OnesCount()) default: // Fallback return OnesCountEmulated(v) } }
2. NEON / Wasm (一部機能のみサポート)
完全な AVX512 命令がない場合、簡易的な実装またはエミュレーションを行います。
//go:build goexperiment.simd && (wasm || arm64) func OnesCount(v simd.Int8s) simd.Int8s { switch x := v.ToArch().(type) { case archsimd.Int8x16: return simd.Int8sFromArch(x.OnesCount()) default: // Fallback to emulation if specific instruction unavailable return OnesCountEmulated(v) } }
3. エミュレーション共通実装 (OnesCountEmulated
)
OnesCountEmulatedプラットフォーム固有の命令がない場合に使用する高速なエミュレーションロジックです。
func OnesCountEmulated(v simd.Int8s) simd.Int8s { a := [2]uint64{} v.ToBits().ReshapeToUint64s().Store(a[:]) a0, a1 := a[0], a[1] m1 := uint64(0x5555555555555555) m2 := uint64(0x3333333333333333) m4 := uint64(0x0f0f0f0f0f0f0f0f) // 高速なポップカウント演算(SIMD 命令なしでも効率的) a0 = (a0 & m1) + ((a0 >> 1) & m1) a1 = (a1 & m1) + ((a1 >> 1) & m1) a0 = (a0 & m2) + ((a0 >> 2) & m2) a1 = (a1 & m2) + ((a1 >> 2) & m2) a0 = (a0 & m4) + ((a0 >> 4) & m4) a1 = (a1 & m4) + ((a1 >> 4) & m4) return simd.LoadUint64s(a[:]).ReshapeToUint8s().BitsToInt8() }
API の交差点と GODEBUG 設定
simd パッケージは、ハードウェアの有無にかかわらず同じコードを記述できるように設計されています。
GODEBUG による動作制御
ビルド時に
GOEXPERIMENT=simd を設定するだけでなく、実行時の環境変数 GODEBUG で SIMD の振る舞いを動的に変更できます。
: 常にエミュレーションを実行(ハードウェア無視)。GODEBUG=simd=0
: 128 ビットベクトルを使用。機能不足時は即座にパニック。GODEBUG=simd=128
: 128 ビットベクトルを使用。一部の機能が利用できない場合でも実行を継続(未サポート指令の使用時のみパニック)。GODEBUG=simd=+128
これにより、Raspberry Pi (NEON あり / PMULL なし) や Apple Silicon エミュレーションなど、環境依存の制限をコードから切り離すことができます。
実装詳細と AST リライト
はパッケージと実装パッケージ(コンパイラーフロントエンドで AST を再編成)を兼ねています。simd- 関数呼び出し時に、
を参照するコードはサイズ特化した型(例:simd.Float32s
,@simd128
)へ自動的に置き換わります。@simd512 - この仕組みにより、プログラマーはハードウェアの差異を意識せずに汎用コードを記述できますが、内部では適切な指令が選択されます。
今後の予定
- Go 1.28:
への SVE サポート追加およびarchsimd
パッケージへの対応。simd - 機能拡張:
, マスク演算の強化、還元演算(Reduce)など、さらに多くの SIMD 演算をサポートする予定です。OnesCount - 堅牢性の向上: ハードウェアベクトル実装を持つが一部操作を欠くプラットフォームでも、完全エミュレーションにダウングレードせずに動作可能な「機能バリエーション」の実装。