メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

新機能実験的

アーキテクチャ・ベクタ幅非依存のポータブルSIMD APIを提供する simd パッケージが GOEXPERIMENT=simd の下で追加された

New experimental simd package

この項目の注釈は AI により生成されており、誤りを含む場合があります。
使用例のコンパイル検証: 検証済み

概要

Go 1.27 では、実験的な simd パッケージが追加された。ポータブルでベクタ幅に依存しない SIMD (Single Instruction, Multiple Data) サポートを提供し、ビルド時の環境変数 GOEXPERIMENT=simd を設定することで有効になる。

simd パッケージは全アーキテクチャで利用可能で、Int8sFloat32s のようにサイズを指定しないベクタ型を提供する。これらは、simd/archsimd パッケージが持つアーキテクチャ固有・ハードウェア対応の操作や、容易にエミュレートできる操作の「スケーラブルな部分集合」をサポートする。ハードウェア命令が利用可能であればそれを使用し、利用できない環境ではソフトウェアエミュレーションにフォールバックする。

導入経緯

本提案は #73787 で説明された「二層アプローチ」の上位層(ポータブルAPI)にあたる最初の実装であり、Go 1.26 で導入されたアーキテクチャ固有の下位API simd/archsimd の上に構築される。提案は #78902 で議論された。

議論タイムライン

  • 2026-05-06: Proposal Review Meeting で議論され、「active」に。広範な反対意見はなく、API詳細についての質問が中心だった。
  • 2026-05-13: 「likely accept」に変更。GOEXPERIMENT による限定公開であり将来の変更を前提とした導入であることが判断材料となった。
  • 2026-05-21: 前回からの追加のコンセンサス変更がなかったため、正式に「accepted」として承認された。

議論のハイライト

  • 固定ベクタ幅(例えば Uint32x8 のように要素数を固定した型)のサポートは、ハードウェアより広い幅をエミュレートする実装コストが大きいことから、現バージョンではスコープ外とされた。
  • simd/archsimd 間の相互変換方式として、ToArch() any を型スイッチで処理する案(Option 1)と、ジェネリクスを用いる案(Option 2b)が検討された。ジェネリックメソッドが型チェッカのクラッシュを引き起こしたため、Option 1 が暫定的に採用され、FromArch はレーン数が期待と異なる場合に panic する方針となった。
  • unsafe.Sizeof(simd.Int32s{}) はスケーラブルなアーキテクチャではコンパイル時に決定できないため、型パラメータの Sizeof と同様に非定数として扱う方向で合意した。これに伴い言語仕様(go.dev/ref/spec#Package_unsafe)の更新が必要になる点も指摘された。
  • メモリ上のレイアウトとエンディアンについては、スライス要素0をベクタ要素0に対応させるリトルエンディアン順を標準とし、ビッグエンディアンの s390x では単一命令のパーミュテーションで対応する方針が示された。
  • レビュー会議で指摘された LoadMask<Size>Slice 系メソッドの存在はAPI生成ツールの不具合であり、プロトタイプ側で既に修正済みであることが確認された。

使用例

Before

package main

import "fmt"

func sumInt8(a, b []int8) []int8 {
	out := make([]int8, len(a))
	for i := range a {
		out[i] = a[i] + b[i]
	}
	return out
}

func main() {
	a := []int8{1, 2, 3, 4}
	b := []int8{10, 20, 30, 40}
	fmt.Println(sumInt8(a, b))
}

After

//go:build goexperiment.simd

package main

import (
	"fmt"

	"simd"
)

func sumInt8(a, b []int8) []int8 {
	va, _ := simd.LoadInt8sPart(a)
	vb, _ := simd.LoadInt8sPart(b)
	vr := va.Add(vb)
	out := make([]int8, vr.Len())
	vr.StorePart(out)
	return out[:len(a)]
}

func main() {
	a := []int8{1, 2, 3, 4}
	b := []int8{10, 20, 30, 40}
	fmt.Println(sumInt8(a, b))
}

移行時の注意

  • 現時点では GOEXPERIMENT=simd を指定してビルドした場合のみ利用可能な実験的機能であり、将来のリリースでAPIが変更される可能性がある。
  • unsafe.Sizeof(simd.Int32s{}) などスケーラブル型を含む型のサイズはコンパイル時定数にならない予定であり、定数式が前提のコードでは注意が必要。
  • reduction(集約)演算のようにハードウェアが直接サポートしない操作は現行APIに含まれていない。必要な場合は ToArch() でアーキテクチャ固有型に降格するか、Store/StorePart でスライスに書き出してから通常のループで集約する必要がある。
  • 固定ベクタ幅(例えば8要素固定の Uint32x8 相当)を移植可能な形で扱いたい場合、現バージョンのAPIではサポートされていない。

実装解説

  • src/simd/doc.go はパッケージ全体に //go:build goexperiment.simd を付与しており、simd パッケージ自体がこのビルドタグでゲートされていることが分かる。同ファイルの BUG コメント(l=89)には、リフレクション経由の呼び出しが動作しないこと、SIMD依存のグローバル変数初期化が動作しないこと、内部で書き換えられた名前がスタックトレースやデバッガに現れうることが明記されている。
  • 各ベクタ型は src/simd/simd_types.go_simd bridge.ZeroSized マーカーフィールドと a, b uint64 のプレースホルダを持つ構造体として定義されている(コメントの通り実際のベクタ幅はこれより大きくなりうる)。このファイルは simd/archsimd/_gen/midway によって自動生成されたものである。
  • 実行時の挙動は src/simd/midway_common.goinit が制御しており、GODEBUG=simd 設定でエミュレーション/実ハードウェアの切り替えやベクタ長(128/256/512)の指定ができる。ハードウェアが機能を全てサポートしない場合は自動的に狭い幅やエミュレーションへ降格し、+ を先頭に付けることでその機能チェックを上書きできる。同ファイルはさらに VectorBitSize()Emulated()HasHardwareCarrylessMultiply() という現在の実行環境を調べるための関数を公開している(l=120)。

関連リンク