What's coming in Go 1.15

Ben Hoyt

Go 1.15で何がやってくるのか

原文は Ben Hoyt により に公開されました。 このブログを購読する

Go 1.15は、Goプログラミング言語の16回目のメジャーバージョンで、8月1日にリリース予定だ。今回は例年より変更点が少ないリリースになるが、主要な変更の多くは舞台裏やツール周りに集中している。たとえば新しいリンカが導入され、ビルド時間の短縮とバイナリサイズの削減が図られる。さらに、言語ランタイムの性能改善、サポートされるアーキテクチャの変更、標準ライブラリの更新なども含まれる。全体として、堅実なアップグレードになるはずだ。

Go 1.0のリリース以来、Goチームは各バージョンでツールと標準ライブラリの改善を継続的に提供してきたが、言語自体の変更については常に慎重な姿勢を保ってきた。多くの言語がリリースごとに大きな言語機能を追加するのに対し、Goは1.0以降のバージョンで提供してきたのはごくわずかな小規模な機能にとどまる。

これは意図的な設計判断だ。1.0のリリース以来、チームが重視してきたのは安定性とシンプルさである。Go 1互換性の約束により、Go 1.0向けに書かれたすべてのプログラムは、すべての1.xバージョンで変更なしに正しく動き続けることが保証されている。Goプログラマは通常これを好ましいことと捉えている。プログラムは「そのまま動き続け」、しかも総じて着実に高速になっていくからだ。

来たる1.15でも予想どおり、言語仕様の変更はほぼ存在せず、改善はツール、コンパイラの性能、そして標準ライブラリに集中している。テックリードのRuss Coxが指摘したように、コア開発者たちはパンデミックを踏まえて1.15では一層慎重になる方針だ。

今後数カ月がどれほど困難になるかは分からない。だから慎重にいこう。デバッグが必要になるような微妙な変更を土壇場で取り込んで、不要なストレスを抱え込むのはやめよう。そうした変更は次のサイクルの冒頭に回し、十分に時間をかけて検証しよう。

[...] Go 1.15はいつもより小規模なリリースになるだろうが、それで構わない。

5月1日にGo 1.15は機能フリーズに入り、Goチームは通常の6カ月ごとのリリースサイクルに従い、8月1日に最終版をリリースする予定だ。

Goの開発モデルは、多くのオープンソース言語とはかなり異なる。この言語はGoogleで設計され、コア開発者の大半も同社に所属している(つまり継続的な開発は実質的にGoogleが支援している)。言語は寛容なBSDスタイルのライセンスの下で提供され、開発はオープンに行われる。一般的な議論はgolang-devメーリングリストで行われ、変更や新機能の提案・議論はGitHubリポジトリのissueで、コードレビューはGerrit上のコード変更(「changelist」または「CL」と呼ばれる)へのコメントを通じて行われる。

新しいリンカ

1.15における最大のツール変更の一つが、完全に書き直されたリンカだ。GoのコアコントリビューターであるAustin Clementsが2019年9月に執筆した新しいリンカの設計ドキュメントでは、書き直しの動機とそれがもたらす改善が詳述されている。新しいリンカには3つの大きな構造的変更がある。

  • リンカからコンパイラへ処理を移すこと。これにより並列化が可能になる。コンパイルは複数のCPU(あるいはマシン)で並列に実行されるが、リンク段階はほぼ必ずビルドの最後に直列で実行しなければならないためだ。さらに、コンパイラの成果物はGoツールによってキャッシュされる。
  • 主要なデータ構造の改善、とりわけ文字列の使用を避けること。現行のリンカは文字列をインデックスとする巨大なシンボルテーブルを使用しているが、新設計ではシンボルに番号を振る手法を用いて、可能な限り文字列の使用を避けている。
  • すべての入力オブジェクトファイルを一度にメモリへ読み込まないようにすること。これにより新しいリンカは大規模なプログラムでメモリ使用量が少なくなり、全体的な割り当ても削減される(現行のリンカは実行時間の20%以上をガベージコレクタに費やしている)。

オリジナルのリンカを手がけたKen Thompsonが引退した今、保守性の問題もある。Clementsは次のように述べている。

オリジナルのリンカは今よりずっとシンプルで、その実装は一人のチューリング賞受賞者の頭の中に収まるものだったため、抽象化やモジュール性はほとんどなかった。残念ながら、リンカが成長し進化する中でも構造の欠如はそのまま残り、そして唯一のチューリング賞受賞者は引退してしまった。

このような大規模で長期的な変更を伴うため、この作業は安定した時点でのみmasterにマージされるdev.linkブランチで進められている。新しいリンカに取り組むThan McIntoshは、1.15に向けてすでに完了した内容について説明した。設計ドキュメントに記された構造的な改善の大部分は完了しており、新しいオブジェクトファイル形式やよりコンパクトなシンボル表現などが含まれる。ビルドはすでに1.14より高速になり、メモリ使用量も減少しているが、一部の機能(たとえばDWARF 5デバッグフォーマットの利用)は1.16まで持ち越しになる。

Clementsは並列化の取り組みや、作業が段階的に導入されている過程について、さらに詳細を付け加えた

その過程で、主要なフェーズの並列化や不要なI/O同期の除去など、他にも多くの改善を行った。リンカに関するこれまでの成果を最大限に活かすため、この変換を「wavefront(波頭)」として実施し、新しい表現から古い表現へ変換するフェーズをリンカ内で徐々に後方へ押しやっていった。まだ完了していない。その変換フェーズは今も残っているが、いつ実行され何を行うかはプラットフォームによって異なる。amd64 ELFプラットフォームではかなり後段で実行され、処理も比較的少ない。他のプラットフォームではそこまで後方にはなく、より多くの処理を行うため、効果はまだそれほど大きくない。いずれにせよ、1.16ではさらなる改善が期待できる。

現状では、リンカはリンク処理の最終段階で出力を依然として古いインメモリ表現へ変換している。おそらく将来のGoのバージョンでは、この最後のステップも新しいリンカに移行され、変換フェーズ自体が完全に削除されることで、リンク時間とメモリ使用量がさらに削減されるだろう。

バイナリの小型化

関連するものとして、Go 1.15でビルドされた実行ファイルのサイズを削減するいくつかの改善がある。Brad Fitzpatrickが示したように、新しいリンカは未使用のコードをより多く除去し、Fitzpatrickの(かなり人為的な)テストプログラムではGo 1.14の8.2MBから1.15では3.9MBへと縮小した。より現実的なプログラムでは、バイナリサイズは3.5%から最大で22%削減されている。筆者が運用しているウェブサーバープログラムでも21.4MBから20.3MBへ、4.9%の削減となった。

これに最も寄与しているのは新しいリンカによる未使用コードの除去と、いくつかの的を絞った改善だ。たとえばClementsによるCL 230544では、実行ファイルに含まれるスタックマップとレジスタマップの数が削減された。これらのマップはGoのガベージコレクタ(GC)が生存オブジェクトを判定するために使われるが、現在ではすべての命令ごとではなく呼び出し箇所でのみ必要とされるようになった。この変更によりgoバイナリのサイズは5.7%削減され、コンパイルやリンクも大幅に高速化された。

Goは実行時に型を検査する能力(reflectパッケージを使用)を持つため、Goのバイナリには大量の型情報が含まれる。Cherry ZhangによるCL 231397では、シンボルの型情報を出力に含めるのはインターフェースに変換される場合に限られるようになった(リフレクションで使えるのはインターフェースに変換された値のみであるため)。この変更によりhello-worldプログラムのサイズは7.2%削減される。

その他にもバイナリサイズに関する細かな改善がいくつかある。たとえばBrad FitzpatrickによるCL 228111では、TLSのクライアントとサーバーの両方のコードのうち片方しか使われていない場合にもう片方を出力に含めないようにし、TLS dial hello worldプログラムのサイズを3.2%削減している。

性能改善

Go 1.15では多くの細かな性能改善が導入されているが、中でも注目すべき2件はGoogle社外の多作なコントリビューターであるJosh Bleecher Snyderによるものだ。CL 216401では、小さな整数をインターフェース値に変換する際のメモリ割り当てを回避し、コンパイルからアセンブルまでの時間を2%改善している。インターフェース値への変換は他言語でいう「ボクシング」に相当し、この最適化は精神的にはPythonの小整数キャッシュに近いが、Goでは静的型付けのため発生頻度ははるかに少ない。

Snyderによる2件目の変更は、コンパイラとランタイム内部に関するCL 226367で、ガベージコレクタのライトバリア呼び出しにより多くのx86レジスタを使えるようにするものだ。GoではGCがユーザーコードと並行して動作する際にヒープ上のデータ整合性を保つため、ライトバリア(ある種のロックのようなもの)が用いられる(詳細はGoのGCに関する詳細な分析を参照)。これによりバイナリがわずかに小さくなり、コンパイル時間が1%改善する。

Michael Knyszeは、メモリアロケータの「mcentral」データ構造を再設計してロック競合を減らすことで、大きなブロックのメモリ割り当てのスループットを大幅に向上させた。新しい割り当てコードは12KB以上のブロックで2倍以上高速になっている。

ツールとポート

Goの「modules」機能(Goの依存関係管理システム)はGo 1.11で初めて導入され、1.13ではモジュールミラーすなわち「proxy」のサポートが追加された。1.15ではフォールバックプロキシのサポートが追加され、モジュールのソースコードをダウンロードする際に最初のホストが失敗した場合にgoツールが代替ホストへフォールバックできるようになった。フォールバックは、GOMODCACHE環境変数の新しい「|」区切り文字で指定する。

Go 1.15では2つの古いポート、darwin/386darwin/armが削除される。これらはmacOSや他のApple製OS向けに32ビットバイナリを提供していた。Fitzpatrickが指摘するように、macOS Catalinaは32ビットアプリの実行をサポートしていないため、これらのポートを削除することでmacOSのビルドマシンを解放できるほか、コンパイラ自体もわずかに小さくなる。これらのポートはGo 1.14リリースで非推奨と告知され、Go 1.15で削除されることになる。

一方、linux/arm64ポートは「first class port」へと昇格した。これはlinux/arm64でビルドが壊れるとリリースがブロックされることを意味し、公式バイナリやインストール手順もGoチームによって提供される。Fitzpatrickが指摘したように、Linuxの64ビットArmは、すでにfirst class portである32ビットArmと少なくとも同等に重要になっている。

Windowsでは、Go 1.15から生成される実行ファイルはデフォルトでアドレス空間配置のランダム化(ASLR)を利用するようになった。ASLRは位置独立コードを用いて起動時にさまざまなデータ領域のアドレスをランダム化し、攻撃者がターゲットアドレスを予測してメモリ破壊のエクスプロイトを作成することを困難にする。

標準ライブラリの追加

Goの標準ライブラリは大規模で比較的安定しており、Go 1.15で追加されたのは比較的マイナーな機能にとどまる。

標準ライブラリのtestingパッケージは非常にミニマルだ。テストやアサーションを記述するためのドメイン固有言語を避け、開発者がすでに知っている素のGoで書くというのがGoの哲学である。しかし、一時ディレクトリの作成は十分に有用であるとコア開発者が判断し、現在のテスト用に一時ディレクトリを遅延生成し、テスト終了時に自動的に削除するTempDir()メソッドの追加が承認された。

net/urlパッケージには、新しいURL.Redacted()メソッドが追加された。このメソッドはURLを文字列として返すが、パスワード部分は伏せ字(xxxxxに置換)で返される。https://username:[email protected]/のようなパスワード付きURLはブラウザではもはや通常使われないが、スクリプトやツールでは依然として驚くほどよく見られる。Redacted()は、:の後の部分を平文で表示しないというRFC 3986のガイドラインに沿って、URLをより安全にログ出力するために利用できる。

実行ファイルにタイムゾーンデータベースの静的なコピーを埋め込めるように、新しいtime/tzdataパッケージが追加された。実行ファイルに約800KB追加されるため、これはオプトインとなっている。time/tzdataパッケージをインポートするか、timetzdataビルドタグを付けてコンパイルすることで有効になる。埋め込みデータベースにより、一部のシステム(特にWindows)でタイムゾーンデータベースへのアクセスがより一貫性のある信頼性の高いものになり、DockerコンテナやGo playgroundのような仮想化環境でも有用となりうる。

おわりに

Goではすべてのバグや機能要望をGitHubのissueで追跡しているため、リリース内容をさらに詳しく知りたい場合はGo 1.15マイルストーンでクローズされたissueの一覧を参照するとよい。1.15の最終リリースまでまだ2カ月以上あるが、gotipツールを使えば自身のコードを最新バージョンで簡単にテストできるし、6月1日に予定されているバイナリ版ベータリリースを待つこともできる。今見つかったバグは、ほぼ確実に1.15最終リリースまでに修正されるだろう。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント