Go言語に、これまでアセンブリ言語を書かなければ手が届かなかった処理能力への近道ができました。CPUが持つSIMD(複数データへの一括処理)機能を、Go 1.26とGo 1.27で実験的なAPIとして使えるようにしたのです。暗号処理やデータ処理、AI関連の計算を高速化できる機能ですが、対応するCPUアーキテクチャごとにベクトルの表現方法がまるで違うという壁があり、その壁をどう越えたのかが今回の見どころです。
Goが実験的に切り開いた、CPUの隠れた計算力への道
CPUには、8個のfloat64の値を1回の命令でまとめて足し算できるような機能が備わっています。これがSIMD(Single Instruction Multiple Data)です。暗号処理からデータ処理、AI関連の計算まで、幅広い処理を速くできる可能性を持っています。実際、Goのガベージコレクタ「Green Tea」も、メモリ上の生きているオブジェクトを探す処理にSIMDを使っているそうです。
問題は、これまでGoからSIMDを使う方法がGoアセンブリを書くことしかなかった点です。かみ砕いて言えば、CPUの奥深くにある高速道路を使うのに、通常のGoのコードでは入り口が見つからず、専用の裏道(アセンブリ言語)を自分で掘るしかなかったということです。裏道を掘る労力に見合うのは、本当に性能が重要な計算処理の核心部分だけでした。結果として、SIMDで速くなる余地があるソフトウェアの多くが、CPUの能力を使い切らないまま動いていたことになります。
この状況に対し、Go 1.26でamd64向けのSIMD APIが導入され、Go 1.27ではarm64(NEON)とwasm向けのAPIが追加されました。ここまでは、アーキテクチャごとに専用のAPIを用意する「archsimd」というパッケージの話です。
Go 1.27はさらに一歩進み、実験的にプラットフォームやベクトルサイズに依存しない「simd」パッケージも導入しました。C++のライブラリ「Highway」を参考にした設計だといいます。目指しているのは、SIMDに対応したプラットフォームでは近アセンブリ並みの性能を保ちながら、SIMDにまだ対応していないプラットフォームでもそれなりに動くエミュレーションを提供することです。現時点でamd64のAVX、AVX2、AVX512、arm64のNEON、wasmのSIMD命令に対応しています。Go公式ブログ
Goが向き合った、CPUごとにバラバラなベクトルの姿
なぜ「プラットフォームに依存しないAPI」がわざわざ必要だったのか。その理由は、SIMDに対応するアーキテクチャ同士のあいだにある違いの大きさにあります。
たとえるなら、同じ「荷物をまとめて運ぶトラック」という機能でも、荷台の大きさがメーカーごとに固定だったり、走ってみないと荷台の大きさが分からなかったりするようなものです。wasmやPowerPC、s390xは128ビットという1種類のベクトル幅しか持ちません。一方amd64は128、256、512ビットの3種類を持ち、loong64は128と256ビットの2種類を持ちます。riscv64にいたっては、128ビットから65536ビットまでのあいだで、2のべき乗という条件付きながらサイズが決まっていない仕様です。arm64も、固定サイズのNEON(128ビット)と、可変サイズのSVE(128〜2048ビット、2のべき乗のみ)という2つの方式を抱えています。
さらに厄介なのは、同じアーキテクチャの中でも、実際にどのサイズに対応しているかは動かしてみないと分からない場合がある点です。amd64であればAVXなのかAVX2なのかAVX512なのか、arm64であればNEONなのかSVEなのか、CPUの機能を都度確かめる必要があります。出典記事はこうした差異の存在と、なぜそれがAPI設計の課題になるかを説明していますが、simdパッケージが内部でどのように実行時判定とコード生成を組み合わせているかの詳細な仕組みまでは踏み込んでいません。この点は今回確認した出典の記載範囲を超えるため、ここでは触れません。
この技術的な難しさについて、個人ブログ「雑記帳」も注目しています。同ブログの記事は、GoがSIMDに対応したことで「ネイティブコンパイルする言語ではSIMDを扱えるのが当たり前」になったとしたうえで、SIMDの難しさとして、Intel intrinsicsの命名規則の分かりにくさや、命令セット拡張ごとの対応状況の違い、命令セットの直交性(同じような演算がベクトル幅を変えても揃って提供されているか)の問題を挙げています。同ブログは、SIMDを自由に使うにはコンパイラを通じて専用命令を書き出す必要があり、そのために言語処理系そのものに手を入れたくなる、という見方を示しています。雑記帳
Hacker Newsのコメント欄では、実際に使ってみた開発者からの声も出ています。あるコメントは、ポータブル版のSIMD(プラットフォームに依存しない書き方)は、プラットフォーム専用に最適化したSIMDに比べて実測で約11%遅かったものの、両方ともSIMDを使わない場合に比べて約5倍速かった、という実測結果を紹介しています。物差しとして読むなら、「移植性を取ると1割ほど速度を譲るが、SIMDを使わない場合との差はそれでも5倍」という比較になります。別のコメントは、正式なベンチマークではないと断りつつ、SIMDを使った作業によって計算処理の性能に体感できる改善があったと述べています。Hacker News
用語補足:SIMD
SIMDとは、1回のCPU命令で複数のデータに同じ計算を一括して行う仕組みです。たとえば8組の数値の足し算を1回の命令で済ませるようなイメージで、ループを回して1つずつ計算するより処理を速くできます。ただし、SIMDが使えるかどうかや、一度に扱えるデータの幅(ベクトルサイズ)はCPUの種類ごとに異なります。今回の記事で紹介した「約11%遅い」「約5倍速い」という数値は、あくまでHacker Newsのコメント投稿者が示した一つの実測例であり、すべての処理やすべてのCPUで同じ差になるとは出典からは判断できません。
注目ニュース
pkg.go.devが提供する、Goパッケージの検索機能
Go言語の公式パッケージ検索サイトである pkg.go.dev では、パッケージ名(例えば「http」や「command」)やシンボル名(例えば「Unmarshal」や「io.Reader」)を使った検索ができます。パッケージ内のシンボルに絞り込んだ検索を行う際には、「golang.org/x #error」のように「#」記号を使ったフィルター機能も用意されています。今回のSIMD関連パッケージを含め、Goの標準ライブラリや外部パッケージの仕様を調べる際の入り口となる場所です。pkg.go.dev
Go Developers Networkが運営する、世界各地の開発者コミュニティ
Go Developers Network(GDN)は、Go言語を使う開発者同士がつながるコミュニティです。世界各地にミートアップグループが組織されており、Go言語やそのエコシステム、開発者や企業がどのように成果を上げているかを学べる場になっています。地域ごとのミートアップグループの検索に加えて、誰でも参加できるリモートのグローバルイベントも用意されています。ウェビナーの開催依頼はフォームから申し込む形式になっています。Meetup
編集後記:ポータブル版とアーキテクチャ専用版、速度差1割強をどう見るか
今回の実験的なSIMD対応で気になるのは、プラットフォームに依存しない「simd」パッケージと、アーキテクチャごとに最適化された「archsimd」パッケージという、2階建ての構成になっている点です。Hacker Newsのコメントにあった「ポータブル版は専用版より約11%遅いが、SIMDなしとの比較では約5倍速い」という実測は、あくまで一つの計算処理における一例に過ぎません。それでも、移植性と性能のどちらを優先するかという選択が、実際に数字として表れている点は興味深いところです。
Go自身も、Go 1.28に向けてSVE対応やベクトルシャッフルなどの演算追加を予定していると出典記事は述べています。ただし、それが実際にどの程度の性能改善につながるかは、現時点の記載からは読み取れません。ポータブル版を選ぶか、アーキテクチャ専用のarchsimdを選ぶかは、扱う処理の性質と、対応したいCPUの範囲次第で変わってきそうです。
