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

Go Proposal Weekly Digest

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

実験的破壊的変更

実験的なsimd/archsimdパッケージがGo 1.27でAMD64向けAPIを改定しつつarm64(Neon)とWebAssemblyの128ビットSIMDに対応した

Experimental simd/archsimd package

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

概要

Go 1.26でGOEXPERIMENT=simdによって導入された実験的なsimd/archsimdパッケージが、Go 1.27で対応アーキテクチャを拡大した。従来のAMD64向けAPIを改定した上で、arm64の128ビット「Neon」SIMDとWebAssemblyの128ビットSIMDへの対応が追加された。いずれもビルド時にGOEXPERIMENT=simd環境変数を設定することで有効になる、まだ安定版とはみなされていない実験的なAPIである。

導入経緯

このパッケージは、アセンブリを書かずにGoからSIMD命令へアクセスする手段を提供する提案(golang/go#73787)に基づく。手書きアセンブリでのSIMD利用には「書くのが難しい」「非同期プリエンプションを妨げる」「小さなカーネルのインライン化を妨げる」という課題があり、それを解決するために提案された。設計方針として、命令に近い低レベルのアーキテクチャ固有APIと、将来追加予定の高レベルな移植可能APIの「二層アプローチ」が採用されている。本Itemが対象とするのは前者の低レベルAPIである。

議論タイムライン

  • 2025-08-20: dev.simdブランチでAMD64向け低レベルSIMDパッケージの試験実装が公開された。
  • 2025-12-16: Go 1.26 RC1で利用可能になり、パッケージ名がsimdからsimd/archsimdに変更された。
  • 2026-01-15: Go 1.26 RC2でいくつかのAPI変更を伴うバグ修正が行われた。
  • 2026-02-11: Go 1.26でRC2からの軽微なバグ修正を経て正式にリリースされた。
  • 2026-06-23: Go 1.27 RC1で、ポータブルなsimdパッケージ(golang/go#78902)の実験的サポート、AMD64向けarchsimd APIの更新、Wasm/ARM64向けarchsimdサポートが追加された。AMD64向けAPIには破壊的変更が含まれる。

議論のハイライト

  • 名前空間の設計を巡る議論: 型ごとの名前(Uint32x4など)にするか、レーン幅ごとの汎用型(Vec128など)にするかで議論があった。提案者は型安全性を重視し、型ごとの明示的な名前を採用する方針を維持した。
  • 命令名 vs 意味のある名前: Ian Lance Taylorらは、Goのアセンブラで採用した「アーキテクチャ横断で共通の命令名を使う」方針が専門家にとってはむしろ分かりにくいと指摘し、命令名をそのまま使うべきだという意見を述べた。一方で提案者は、コードの「書き手」より「読み手」を重視し、意味のある名前を維持する方針を説明した。
  • 移植性への懸念: 低レベルAPIを単一パッケージにまとめる「best-effort portability」の方針に対し、syscallパッケージがOS別パッケージに分割された経緯を引き合いに、アーキテクチャ別パッケージに分けるべきという反論があった。
  • CPU機能検出: golang.org/x/sys/cpuとの重複を指摘する声があったが、コンパイラが値を定数として扱えるようにするため、simd/archsimdパッケージ内に専用の機能検出関数(X86.AVX()など)を持たせる設計となった。
  • 実装が進むにつれ、GetLo/GetHiIsZero(VPTEST)、ClearAVXUpperBits(VZEROUPPER)などのAPI追加や、構造体スピルに関するコンパイラのバグ修正が活発に行われた。
  • 2026-06-23時点のissue更新では、Go 1.27でAMD64 API改定に加え、arm64・Wasm対応が追加されたことと、AMD64側に破壊的なAPI変更が含まれることが明記されている。

使用例

Before

package main

import "fmt"

func addInt32(x, y []int32) []int32 {
	out := make([]int32, len(x))
	for i := range x {
		out[i] = x[i] + y[i]
	}
	return out
}

func main() {
	x := []int32{1, 2, 3, 4}
	y := []int32{10, 20, 30, 40}
	fmt.Println(addInt32(x, y))
}

After

//go:build goexperiment.simd

package main

import (
	"fmt"

	"simd/archsimd"
)

func main() {
	if !archsimd.X86.AVX() {
		fmt.Println("AVX unavailable")
		return
	}

	x := archsimd.LoadInt32x4([]int32{1, 2, 3, 4})
	y := archsimd.LoadInt32x4([]int32{10, 20, 30, 40})
	sum := x.Add(y)

	out := make([]int32, sum.Len())
	sum.Store(out)
	fmt.Println(out)
}

移行時の注意

simd/archsimdはAMD64向けにAPIの破壊的変更を含んでいるため、Go 1.26のGOEXPERIMENT=simdビルドで既にこのパッケージを使っていたコードは、Go 1.27へ更新する際にコンパイルが通らなくなる可能性がある。また、APIは依然として実験的であり、Go 1の互換性保証の対象外である点に変わりはない。

実装解説

Go 1.27時点のソースツリー(src/simd/archsimd)を確認すると、アーキテクチャごとに生成方法が異なる。AMD64向けの型・演算・CPU機能検出はsimdgen(types_amd64.gocpu.go)で、arm64向けも同じsimdgen(arm64向けデータソースから、types_arm64.go)で生成されている一方、WebAssembly向けは別ツールのwasmgen(types_wasm.go)で生成されている(generate.goにあるgo:generate行に両方のツールが列挙されている)。

CPU機能検出はアーキテクチャごとに分離されている。AMD64向けのX86Features(cpu.go)はAVX/AVX2/AVX512系列など多数のフィーチャーフラグをinternal/cpu経由で提供するのに対し、arm64向けのARM64Features(cpu_other.go)は現時点でPMULLのみを公開しており、Neonの基本演算自体はCPU機能チェックなしに使える設計になっている(例えばops_arm64.goInt8x16.AddCPU Feature: NEONとコメントされているのみで、対応するHas関数は存在しない)。

AMD64のAPI改定と見られる部分として、各ベクタ型に低レベルの配列ベースのLoadXArray/StoreArray(コンパイラ組み込み、例: types_amd64.goLoadFloat32x4Array/StoreArray)と、それをラップしてスライスを受け取るLoadX/Store(パニックするラッパー、slice_gen_amd64.goLoadInt32x4/Store)の2段構成になっている。この分離により、配列ポインタを介した低レベルの組み込み命令と、範囲チェック付きの利用しやすいスライスAPIが明確に分かれている。

関連リンク