フューチャー技術ブログ

Go 1.17連載が始まります: コンパイラとgo mod

前回のフューチャー技術ブログで行ったGo 1.16連載に引き続き、今回もGo 1.17の集中連載を行います。

Go 1.17のリリースの足音が聞こえてきました。1.16のgo:embedのような「うぉっ」と声が出るような大きな新機能はなく、APIが変わらずに勝手に改善されるようなものと、小粒なAPIの追加が多い感じです。ジェネリクスは何も変更がなければ最短で1.17という話もありましたが、今回は入っていません。

今回の連載ではこれらの変更をリレー形式で紹介していきます。

公開日 寄稿者 記事
8月10日 澁川喜規 コンパイラとgo mod(この記事)
8月11日 真野隼記 encoding/csv
8月12日 伊藤真彦 testingの新機能
8月17日 玉木竜二 rune
8月18日 辻大志郎 go get
8月19日 宮崎将太 New warnings for Is, As and Unwrap methods
8月20日 市川燿 sync/atomic

また毎回恒例のGo 1.17リリースパーティも開催予定です。ぜひご参加ください。

コンパイラ

関数呼び出しが5%高速化

1.16の時に予告されていたレジスタベースの関数呼び出しが入りました。呼び出し速度が5%、バイナリサイズが2%改善されました。特にコードの変更なく恩恵に授かれます。

あとはpanic時のスタックトレースの表示が改善され、今まで16進数で表示されていたのが改善されたらしいのですが、よくわかりませんでした。16進数は出なくなりましたが、試したが、…となるだけでした。

クロージャを含む関数がインライン展開されるようにもなりました。これもコード修正必要なく、パフォーマンスアップにつながる可能性があります。

なお、unsafeで怪しく引数を触ったりするとトラブルが発生する可能性がありますが、通常のGoの使用方法では問題になることはなさそうです。

build constraintのコメントが変更

以前は次のようなコメント形式でした。

// +build windows

package main

これが次のようになります。

//go:build windows
// +build windows

package main

今は新旧併存ですが、1.18が出て1.16サポートが切れたら新コメントのみ対応になると思われます。

//go:generateとか//go:noinlineと見た目が揃いますね。旧コメントは後ろに空行がないといけない(しかし、他のgodocなどは空行があってはダメ)など、いろいろ気難しい感じでしたが、新コメントはここは緩和されています。

go fmtでは互換性を考えて新・旧のコメントを生成したり、go vetでは新・旧のコメントの内容が違っていたら警告を出すなど、周辺エコシステムもこれを受けて変更されています。

新しいターゲットの追加と削除

macOSのサポートが10.13 High Sierra以降のみになる、WindowsのARM64がサポートされる、64bit MIPS on OpenBSDがサポート、すべてのARM64環境でスタックフレームポインタが維持されるようになった(Linux/macOS/iOSは元々維持されていたのが全展開になった)、loong64がGOARCHの予約語に追加(サポートはまだ)、という感じでした。

WindowsのARM64は今後広がっていくんですかね。ARM64機よりも、M1 mac上でQEMUで動かす方が台数が増えそうな気がしないでもないですが・・・Surface Pro Xは少しお高かったので、安い機種が出てきたら興味はあります。

loong64とは何かよくわからなかったのですが、中国製CPUの龍芯ですね。MIPS64のカスタム版みたいなやつのようです。面白そう。

go mod

今回一番変更が多かったのがgo mod周りですね。

Module Graph Pruning (Lazy Module Loading)

github.com/rs/xidとgithub.com/fatih/colorをgo getしてみたコードです。

go.mod(1.16)
module go116sample

go 1.16

require (
github.com/fatih/color v1.12.0
github.com/rs/xid v1.3.0
)
go.mod(1.17)
module go117sample

go 1.17

require github.com/fatih/color v1.12.0

require (
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
)

Go 1.17の方は間接的に呼び出されているパッケージの情報まで含まれています。Go 1.16の場合は全モジュールグラフを取得する場合はどんどん子供を辿っていって完成させる必要がありますが、Go 1.17は必要な依存関係がすべて含まれているため、go.modを調べる必要性が減っています。

詳しくはこちらのGo Modの説明か、このModule Graph PruningのDesign Document参照と書かれています。

一見、pruningという言葉を言葉を見ると、刈り込みとか削除という意味なので1.16の動作の方が新しい挙動っぽくも感じたのですが、必要なサブパッケージの情報まで読み込みをしてflatten化して整理する、みたいなニュアンスなのかもしれません。英語難しい。

Design Documentの説明によると背景としてはこんな感じっぽいです

  • 今まではモジュールグラフを見ないと必要なライブラリとバージョンが決定できなかった
  • go.modを提供していないライブラリがあると、実行するたびに条件が変わりがちだし、必要なパッケージのバージョンが引き上げられたりする可能性もあるし、参照先がなくなってキャッシュされていない場合にビルドできなくなったりする
  • 選択されたバージョンだけではなく、indirectな依存バージョンも登場するため、replaceディレクティブを使うのば難しかった
  • 大量のgo.modを取得しなければならないことを避けたい

必要になるまでgo.modの読み込みを遅延させる、go.modの読み込みの必要性を下げることが「Lazy Module Lodaing」という感じのようですね。

少ないgo.modの読み込みで完了するということですかね。ビルドが早くなったりしそうな気がしますが、とくにそういう効果は書かれてませんね。まあ条件が難しいから数値化もしにくそう。

go modコマンドでGoバージョンの更新や上書きが可能

go mod周りは前述の変更などバージョンによって挙動が変わったりします。そのため、go.modのgoディレクティブのバージョン指定が大切になってきます。なお、今回からgo directivesがない時は、現在のバージョンではなく、go 1.11とみなすことになりました(1.12からはgo mod initをすれば勝手にgoディレクティブが入る)。

go 1.17

そのためにgo mod tidyコマンドのオプションが増えています。-goを設定すると、go.modをそのバージョンの方式で上書きして更新します。-compatはgo.modの変更はしません。指定のバージョンで実行したものとして実行する感じですかね?

$ go mod tidy -go=1.17     # 更新
$ go mod tidy -compat=1.17 # 旧バージョンサポートを上書きして実行

非推奨(deprecated)宣言

もうこのバージョンは古いよ、とかこのパッケージはメンテを停止したよ、ということをパッケージ開発者が情報を付与できるようになりました。

// Deprecated: use example.com/mod/v2 instead.
module example.com/mod

goコマンドでビルドしたりパッケージ取得する場合は警告が出るようになりますし、次のコマンドで表示できるとのこと。

$ go list -m -u

go getの認証とかもろもろ

-insecureで安全なHTTPS接続でなくても接続できるオプション(ついでにチェックサムのチェックなどを無視する)がありましたが、それがなくなるようです。GOINSECURE環境変数を使えとのこと。

あと、sshの認証が必要な場合に認証ダイアログが出るようになります。

go mod downloadの挙動の変化

go mod download allallがない限りは、go.sumを更新することはなくなりました。1.15の挙動に戻るとのこと。

go mod vendorでバージョン情報が付与

go mod vendorしたときに生成されるvendor/modules.txtファイル。次のように、ちょびっとだけexplictの後ろにバージョンが付与されるという違いあります。それぞれのパッケージのgo.modに書かれていたバージョンが抽出されるようです。それぞれのパッケージはこのバージョンでビルドされる、go.modは無視されると書かれていますが、それぞれのバージョンのコンパイラがあるわけではないし、よくわからないですね。

go1.17
# github.com/fatih/color v1.12.0
## explicit; go 1.13
github.com/fatih/color
go1.16
# github.com/fatih/color v1.12.0
## explicit
github.com/fatih/color

go runで外部パッケージを直接実行

# バージョンを付けないとエラー

% go run github.com/Songmu/gocredits/cmd/gocredits
no required module provides package github.com/Songmu/gocredits/cmd/gocredits; to add it:
go get github.com/Songmu/gocredits/cmd/gocredits

# 付けるとOK
% go run github.com/Songmu/gocredits/cmd/gocredits@latest

go generateでもダウンロードせずに直接実行できました。go modには追加されないため、ライブラリ作者だけが必要なツールはこちらの書き方にしておけば、ライブラリユーザーがそのツールをダウンロードする必要はなくなりそうです。

//go:generate go run github.com/Songmu/gocredits/cmd/gocredits@latest -w

1.16では@latestをつけてもエラーになり、build constraintでビルド対象にならないtools.goファイルを作ってgo getしてgo.modにも記録してあげるのがバッドノウハウとなっていました。あまり直感的とは言えない動作だったので、1.17の方式はありがたいですね。1.15ではできていましたが。

// +build tools

// go 1.16までのやり方

package mypackage

import (
_ "github.com/Songmu/gocredits/cmd/gocredits"
)