[インデックス 17907] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc)におけるマップの内部構造(ハッシュバケット、マップヘッダ、イテレータ)の名前の表示方法に関する特殊処理を修正するものです。この修正により、生成されるバイナリサイズが約1%削減される効果があります。
コミット
- コミットハッシュ:
f238049a0073538caecfad1c60238a271426f43c - Author: Keith Randall khr@golang.org
- Date: Tue Dec 3 14:27:08 2013 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f238049a0073538caecfad1c60238a271426f43c
元コミット内容
cmd/gc: fix special-casing of the printed names of map internal structures.
Shaves 1% off of binary size.
update #6853
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/35940047
変更の背景
この変更は、Goのバイナリサイズが増大しているという問題(Issue #6853: "all: binaries too big and growing")に対応するための一環として行われました。Goのバイナリサイズは、特にリリース間で顕著に増加しており、これはバグと見なされていました。バイナリサイズは、シンボルテーブル(symtab)、ランタイム型情報(pclntab)、リフレクション型、静的Go文字列データなど、様々な要素によって構成されます。
このコミットは、マップの内部構造の型名が不必要に長く、複雑に表示されることで、コンパイラが生成する型情報やシンボル情報が肥大化し、結果としてバイナリサイズが増加していた問題を解決することを目的としています。特に、マップの内部構造の型名が再帰的に展開されることで、非常に長い名前が生成され、これがバイナリサイズに悪影響を与えていました。この修正は、その肥大化を抑えるための具体的な対策の一つです。
前提知識の解説
Goコンパイラ (cmd/gc)
cmd/gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスでは、型情報、シンボル情報、デバッグ情報など、様々なメタデータが生成され、最終的なバイナリに埋め込まれます。
Goのマップの内部構造
Goのマップ(map[K]V)は、内部的にはハッシュテーブルとして実装されています。その実装には、いくつかの内部的な型が関与しています。
- ハッシュバケット (
map.bucket): マップのキーと値のペアを実際に格納するデータ構造です。ハッシュ衝突を扱うために、複数のエントリを保持できる場合があります。 - マップヘッダ (
map.hdrまたはhmap): マップ全体のメタデータ(要素数、バケットの数、ハッシュシードなど)を管理する構造体です。 - ハッシュイテレータ (
map.iterまたはhiter): マップをイテレート(for rangeループなど)する際に使用される内部的な状態を保持する構造体です。
これらの内部構造は、Goの型システムの一部として扱われ、コンパイラによって型情報が生成されます。
型のフォーマット (typefmt)
Goコンパイラ内部には、型情報を文字列としてフォーマットするための関数(typefmtなど)が存在します。これは、デバッグ情報やシンボルテーブル、リフレクション情報などで型名が必要とされる際に使用されます。以前の実装では、マップの内部構造の型名が再帰的に展開されるようなロジックが含まれており、これが冗長な型名の生成につながっていました。
技術的詳細
このコミットの核心は、Goコンパイラがマップの内部構造の型名を生成する際のロジックを最適化し、冗長な名前の生成を避けることにあります。
以前のfmt.cのtypefmt関数では、t->hmap != Tやt->hiter != Tといったチェックを通じて、マップのバケットやイテレータの型を特別扱いし、map.bucket[%T]%Tやmap.iter[%T]%Tのような形式で出力していました。しかし、この処理は、内部構造がどのマップ型に属しているかを直接的に参照する仕組みが不足していたため、不必要に複雑な型名が生成される可能性がありました。
このコミットでは、Type構造体に新たにmapフィールドが追加されました。このmapフィールドは、マップの内部構造(bucket, hmap, hiter)から、それらが属する元のマップ型への逆参照を提供します。
これにより、fmt.cのtypefmt関数は、マップの内部構造の型をフォーマットする際に、その内部構造がどのマップ型に属しているかをt->mapを通じて直接確認できるようになりました。具体的には、t->map->bucket == t、t->map->hmap == t、t->map->hiter == tといったチェックを行うことで、現在フォーマットしようとしている型が、特定のマップ型のバケット、ヘッダ、またはイテレータのいずれであるかを正確に識別します。
そして、識別された内部構造に対して、map.bucket[%T]%T、map.hdr[%T]%T、map.iter[%T]%Tといった簡潔な形式で型名を生成します。ここで、%Tには元のマップ型のキーと値の型がそれぞれ埋め込まれます(t->map->downとt->map->type)。これにより、再帰的な型名の展開が抑制され、より短く、かつ意味的に正確な型名が生成されるようになります。
この変更は、reflect.c内のmapbucket, hmap, hiter関数にも影響を与えます。これらの関数は、マップの内部構造の型を生成する際に、新しく追加されたmapフィールドに、その内部構造が属する元のマップ型への参照を設定するように修正されました。これにより、Type構造体間の正しいリンクが確立され、fmt.cでの型名生成が正しく機能するようになります。
結果として、コンパイラが生成する型情報に含まれる型名が短縮され、これが最終的なバイナリサイズの削減(約1%)に貢献します。
コアとなるコードの変更箇所
src/cmd/gc/fmt.c
typefmt関数内のcase TSTRUCT:ブロックが変更されました。
- 以前は
t->hmap != Tやt->hiter != Tといったチェックでマップの内部構造を識別していましたが、これらが削除されました。 - 代わりに、
t->map != Tというチェックが追加され、その内部でt->map->bucket == t、t->map->hmap == t、t->map->hiter == tという条件分岐が追加されました。 - これにより、現在処理している型が、どのマップの内部構造(バケット、ヘッダ、イテレータ)であるかを正確に判断し、それぞれに対応する簡潔な型名(例:
"map.bucket[%T]%T")を生成するように変更されました。 - 未知の内部マップ型が検出された場合には
yyerror("unknown internal map type");が呼び出されるようになりました。
src/cmd/gc/go.h
struct Type定義に新しいフィールドが追加されました。
Type* map; // link from the above 3 internal types back to the map type.このフィールドは、bucket,hmap,hiterといった内部型から、それらが属する元のマップ型への逆参照を保持します。
src/cmd/gc/reflect.c
mapbucket, hmap, hiter関数の内部で、新しく追加されたmapフィールドに適切な値が設定されるように変更されました。
mapbucket関数内で、bucket->map = t;が追加されました。hmap関数内で、h->hmap = t;がh->map = t;に変更されました。hiter関数内で、i->hiter = t;がi->map = t;に変更されました。
コアとなるコードの解説
src/cmd/gc/fmt.cの変更
この変更の目的は、マップの内部構造の型名が冗長になるのを防ぐことです。以前は、マップの内部構造(例: map.bucket)の型をフォーマットする際に、その内部構造がどのマップ型(例: map[string]int)に属しているかを直接的に知る方法がありませんでした。そのため、型名が再帰的に展開され、非常に長くなる可能性がありました。
新しいコードでは、Type構造体にmapフィールドが追加されたことで、内部構造の型(t)から、それが属する元のマップ型(t->map)への参照が可能になりました。これにより、typefmt関数は、t->map->down(キーの型)とt->map->type(値の型)を直接参照して、map.bucket[KeyType]ValueTypeのような簡潔な形式で型名を生成できるようになりました。これにより、不必要な詳細が型名に含まれることがなくなり、バイナリサイズが削減されます。
src/cmd/gc/go.hの変更
Type* map;フィールドの追加は、マップの内部構造と元のマップ型との間の双方向リンクを確立するために不可欠です。このリンクがなければ、fmt.cで内部構造の型をフォーマットする際に、元のマップ型のキーと値の型を取得することができません。このフィールドは、コンパイラが型情報を正確に追跡し、効率的に処理するための重要なメタデータとなります。
src/cmd/gc/reflect.cの変更
mapbucket, hmap, hiter関数は、それぞれマップのバケット、ヘッダ、イテレータの型を生成する役割を担っています。これらの関数でmapフィールドに元のマップ型への参照(t)を設定することは、go.hで定義された新しいリンクを実際に構築する作業です。これにより、コンパイラがこれらの内部構造の型を生成する際に、それらがどのマップ型に属しているかというコンテキストが正しく保持され、fmt.cでの型名生成ロジックが期待通りに機能するようになります。
これらの変更は全体として、Goコンパイラが生成する型情報の効率を向上させ、結果としてGoプログラムのバイナリサイズを削減するという、パフォーマンスとリソース使用量に関する重要な改善に貢献しています。
関連リンク
- Go Issue #6853: https://github.com/golang/go/issues/6853
参考にした情報源リンク
- Go issue 6853, titled "all: binaries too big and growing," is a GitHub issue in the
golang/gorepository. It was opened on November 30, 2013, and discusses the significant increase in Go binary sizes across releases. (Source: Google Web Search for "Go issue 6853")