[インデックス 14396] ファイルの概要
このコミットは、Go言語の reflect
パッケージに ArrayOf
, ChanOf
, MapOf
, SliceOf
といった新しい型生成関数を追加し、それに伴うランタイムおよびリンカの内部構造の最適化と変更を行うものです。主な目的は、実行時に動的に複合型(配列、チャネル、マップ、スライス)を生成・取得する機能を提供しつつ、ランタイムの型表現を効率化することにあります。
コミット
commit 1120982590113361a985dffa8963283cebaa70c7
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 13 13:06:29 2012 -0500
reflect: add ArrayOf, ChanOf, MapOf, SliceOf
In order to add these, we need to be able to find references
to such types that already exist in the binary. To do that, introduce
a new linker section holding a list of the types corresponding to
arrays, chans, maps, and slices.
To offset the storage cost of this list, and to simplify the code,
remove the interface{} header from the representation of a
runtime type. It was used in early versions of the code but was
made obsolete by the kind field: a switch on kind is more efficient
than a type switch.
In the godoc binary, removing the interface{} header cuts two
words from each of about 10,000 types. Adding back the list of pointers
to array, chan, map, and slice types reintroduces one word for
each of about 500 types. On a 64-bit machine, then, this CL *removes*
a net 156 kB of read-only data from the binary.
This CL does not include the needed support for precise garbage
collection. I have created issue 4375 to track that.
This CL also does not set the 'algorithm' - specifically the equality
and copy functions - for a new array correctly, so I have unexported
ArrayOf for now. That is also part of issue 4375.
Fixes #2339.
R=r, remyoudompheng, mirtchovski, iant
CC=golang-dev
https://golang.org/cl/6572043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1120982590113361a985dffa83283cebaa70c7
元コミット内容
このコミットは、Goの reflect
パッケージに以下の新しい関数を追加します。
ArrayOf(count int, elem Type) Type
: 指定された要素型と長さを持つ配列型を返します。ChanOf(dir ChanDir, t Type) Type
: 指定された方向と要素型を持つチャネル型を返します。MapOf(key, elem Type) Type
: 指定されたキー型と要素型を持つマップ型を返します。SliceOf(t Type) Type
: 指定された要素型を持つスライス型を返します。
これらの関数をサポートするために、既存のバイナリ内に存在する対応する型への参照を見つける必要があり、そのために新しいリンカセクションが導入されます。
また、この新しいリンカセクションのストレージコストを相殺し、コードを簡素化するために、ランタイム型の表現から interface{}
ヘッダが削除されます。これは以前のバージョンで使用されていましたが、kind
フィールドによって冗長になっていました。kind
フィールドによるスイッチの方が型スイッチよりも効率的です。
この変更により、godoc
バイナリでは、約10,000個の型から interface{}
ヘッダを削除することで各型から2ワードが削減されます。一方、配列、チャネル、マップ、スライス型へのポインタのリストを追加することで、約500個の型に対して1ワードが再導入されます。結果として、64ビットマシンでは、この変更によりバイナリから純粋に156KBの読み取り専用データが削減されます。
このコミットには、正確なガベージコレクションに必要なサポートは含まれていません(Issue 4375で追跡)。また、新しい配列の algorithm
(特に等価関数とコピー関数)が正しく設定されていないため、ArrayOf
は一時的に非公開(unexported)とされています。これもIssue 4375の一部です。
このコミットは Issue 2339 を修正します。
変更の背景
Go言語の reflect
パッケージは、プログラムの実行時に型情報を検査・操作するための強力な機能を提供します。しかし、Goの初期バージョンでは、reflect
パッケージを通じて動的に新しい複合型(例えば、[]int
や map[string]interface{}
のような型)を生成する直接的なAPIがありませんでした。これは、ジェネリックプログラミングや、スキーマに基づいて動的にデータ構造を構築するような高度なリフレクションを必要とするシナリオにおいて、開発者に不便を強いていました。
このコミットの主な背景は以下の点にあります。
- 動的な型生成の必要性:
reflect
パッケージの利用者が、コンパイル時に存在しない、あるいは型パラメータに依存するような複合型を、実行時にプログラム的に構築したいというニーズがありました。例えば、データベースのスキーマからGoの構造体を動的に生成したり、RPCフレームワークで任意の型の引数を扱ったりする場合などです。 - 既存型の再利用とメモリ効率: 新しい型を動的に生成する際に、もし同じ構造を持つ型が既にバイナリ内に存在する場合、それを再利用することでメモリ使用量を削減し、バイナリサイズを小さくすることができます。そのためには、リンカがバイナリ内の既存の型情報を
reflect
パッケージに公開するメカニズムが必要でした。 - ランタイム型表現の最適化: Goのランタイムは、すべての型に関するメタデータを保持しています。このメタデータの構造は、効率性とメモリ使用量に直結します。以前のランタイム型表現には
interface{}
ヘッダが含まれていましたが、これはkind
フィールドの導入により冗長になっていました。この冗長性を排除することで、バイナリサイズとメモリフットプリントを削減する機会が生まれました。
これらの背景から、reflect
パッケージに ArrayOf
, ChanOf
, MapOf
, SliceOf
といった動的な型生成関数を追加し、それに伴うランタイムとリンカの内部構造の変更が計画されました。特に、メモリ効率の改善は、Goの設計哲学である「シンプルさと効率性」に合致する重要な目標でした。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の内部構造と概念に関する前提知識が必要です。
-
Goの型システムと
reflect
パッケージ:- 静的型付け: Goは静的に型付けされた言語であり、通常、すべての型はコンパイル時に決定されます。
interface{}
(空インターフェース): 任意の型の値を保持できる特殊なインターフェース型です。内部的には、値の型情報 (_type
ポインタ) と値そのもの (data
ポインタ) の2つのワードで構成されます。reflect.Type
とreflect.Value
:reflect
パッケージは、実行時にGoの型と値を検査・操作するためのAPIを提供します。reflect.Type
は型に関するメタデータ(名前、種類、メソッドなど)を表し、reflect.Value
は値そのものを表します。Kind
:reflect.Type
のKind()
メソッドは、その型がプリミティブ型(Int
,String
など)、複合型(Array
,Slice
,Map
,Chan
,Struct
,Func
,Ptr
,Interface
など)のいずれであるかを示す列挙型を返します。
-
Goランタイムの型表現:
- Goのランタイムは、プログラムで使用されるすべての型に関する詳細なメタデータをメモリ上に保持しています。これらの型情報は、ガベージコレクション、インターフェースの動的ディスパッチ、
reflect
パッケージの機能などに利用されます。 - 以前のGoのランタイム型構造では、
commonType
という基底構造体がinterface{}
ヘッダを持っていました。これは、型情報をinterface{}
として扱うためのものでしたが、kind
フィールドが導入されたことで、型を識別するための主要な手段がkind
になり、interface{}
ヘッダは冗長になっていました。
- Goのランタイムは、プログラムで使用されるすべての型に関する詳細なメタデータをメモリ上に保持しています。これらの型情報は、ガベージコレクション、インターフェースの動的ディスパッチ、
-
Goのコンパイラ (
cmd/gc
) とリンカ (cmd/ld
):- コンパイラ: Goのソースコードを中間表現に変換し、最終的にアセンブリコードを生成します。この過程で、型情報も生成され、リンカが利用できる形式で出力されます。
- リンカ: コンパイラが生成したオブジェクトファイルやライブラリを結合し、実行可能なバイナリを生成します。リンカは、プログラムのメモリレイアウトを決定し、シンボルを解決し、様々なセクション(
.text
(コード),.rodata
(読み取り専用データ),.data
(初期化済みデータ),.bss
(ゼロ初期化データ) など)にデータを配置します。 - シンボル: コンパイラやリンカが扱う、関数、変数、型などの名前付きエンティティです。シンボルは、そのアドレスやサイズ、種類などの情報を持っています。
- リンカセクション: 実行可能ファイル内の論理的な区画で、特定の種類のデータやコードを格納します。例えば、
.text
は実行可能な命令を、.rodata
は定数や読み取り専用のデータを格納します。
-
unsafe
パッケージ:- Goの
unsafe
パッケージは、Goの型システムを迂回してメモリを直接操作するための機能を提供します。これには、unsafe.Pointer
(任意のポインタ型とuintptr
間で変換可能) やunsafe.Offsetof
(構造体フィールドのオフセットを取得) などが含まれます。ランタイムやreflect
パッケージのような低レベルのコードで、メモリレイアウトを直接操作するために使用されます。
- Goの
-
ハッシュ関数 (FNV-1):
- FNV (Fowler-Noll-Vo) ハッシュ関数は、高速で衝突が少ないことで知られる非暗号学的ハッシュ関数です。このコミットでは、新しい型を生成する際に、その型のハッシュ値を計算するために使用されます。ハッシュ値は、型を識別したり、ハッシュテーブルで型を検索したりする際に利用されます。
これらの知識を前提として、このコミットがGoの内部でどのように型情報を管理し、動的な型生成を可能にしているかを深く理解することができます。
技術的詳細
このコミットの技術的詳細は、主に以下の3つの側面から構成されます。
-
ランタイム型表現の変更 (
commonType
からrtype
への移行):reflect.Type
の内部構造の簡素化: 以前はreflect.Type
の基底となる構造体はcommonType
と呼ばれ、その中にruntimeType interface{}
というフィールドが含まれていました。このruntimeType
は、Goのインターフェースの内部表現(型情報とデータポインタの2ワード)を利用して、型そのものを指し示していました。しかし、commonType
自体がkind
フィールドを持つようになったことで、このinterface{}
ヘッダは冗長になりました。rtype
の導入: このコミットでは、commonType
がrtype
にリネームされ、runtimeType interface{}
フィールドが削除されました。代わりに、ptrToThis *rtype
のように、直接*rtype
へのポインタを持つようになりました。これにより、各型情報のメモリフットプリントが削減されます(64ビットシステムで2ワード削減)。- 影響範囲: この変更は、
reflect
パッケージ内のすべての型構造体(arrayType
,chanType
,funcType
,interfaceType
,mapType
,ptrType
,sliceType
,structType
)に波及し、これらが埋め込むcommonType
がrtype
に変更されました。また、reflect.Value
の内部表現や、ランタイムとのインターフェース(unsafe_New
,unsafe_NewArray
など)も*rtype
を直接扱うように変更されました。
-
動的な型生成関数 (
ArrayOf
,ChanOf
,MapOf
,SliceOf
) の実装:- キャッシュ機構: これらの新しい型生成関数は、まず
lookupCache
というキャッシュをチェックします。これにより、同じ構造を持つ型が複数回要求された場合に、既存の型を再利用して重複生成を防ぎます。 - 既存型の検索 (
typelinks
とtypesByString
): キャッシュに存在しない場合、これらの関数はバイナリ内に既に存在する型の中から、要求された構造に合致する型を検索します。この検索は、ランタイムが提供するtypelinks()
関数(後述)によって取得される型情報のリストと、typesByString
ヘルパー関数(型の文字列表現でフィルタリング)を利用して行われます。 - 新しい型の構築: 既存の型が見つからない場合、
reflect
パッケージはunsafe
パッケージを駆使して、新しい型構造体(例:chanType
,mapType
)をメモリ上に動的に構築します。この際、プロトタイプとなる既存の型(例:(chan unsafe.Pointer)(nil)
の型情報)をコピーし、必要なフィールド(要素型、キー型、長さ、ハッシュ値など)を上書きします。 - ハッシュ計算: 新しい型のハッシュ値は、FNV-1ハッシュ関数 (
fnv1
) を用いて計算されます。これは、型の同一性を効率的に比較するために重要です。
- キャッシュ機構: これらの新しい型生成関数は、まず
-
リンカとランタイムによる型情報の公開 (
.typelink
セクション):typelink
シンボルの導入: コンパイラ (cmd/gc
) は、配列、チャネル、マップ、スライスといった複合型が定義される際に、その型に対応するgo.typelink.
で始まる特殊なシンボルを生成するようになりました。これらのシンボルは、実際の型情報へのポインタを保持します。.typelink
リンカセクション: リンカ (cmd/ld
) は、これらのgo.typelink.
シンボルを収集し、バイナリ内の新しい読み取り専用セクションである.typelink
に配置します。このセクションは、バイナリ内のすべての動的に検索可能な複合型へのポインタのリストとして機能します。- ランタイムからのアクセス: ランタイム (
src/pkg/runtime/iface.c
) にreflect·typelinks
という新しい関数が追加されました。この関数は、リンカによって公開された.typelink
セクションの開始アドレスと終了アドレスを利用して、バイナリ内のすべてのtypelink
情報をreflect
パッケージにスライスとして提供します。これにより、reflect
パッケージはバイナリ内の既存の複合型を効率的に列挙・検索できるようになります。 - メモリ効率の改善: コミットメッセージにあるように、
interface{}
ヘッダの削除によるメモリ削減効果は、新しい.typelink
セクションの追加によるメモリ増加を上回り、全体としてバイナリサイズの削減に貢献しています。
これらの変更は、Goの reflect
パッケージの柔軟性を大幅に向上させると同時に、ランタイムのメモリ効率を維持・改善するための、コンパイラ、リンカ、ランタイム、および標準ライブラリにまたがる包括的な設計と実装を示しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイル群に集中しています。
-
src/pkg/reflect/type.go
:commonType
構造体がrtype
にリネームされ、runtimeType interface{}
フィールドが削除されました。- すべての複合型(
arrayType
,chanType
,mapType
,sliceType
など)の定義が、埋め込みフィールドとしてcommonType
の代わりにrtype
を使用するように変更されました。 Type
インターフェースのメソッドシグネチャが*commonType
から*rtype
を受け取るように変更されました。typelinks()
(ランタイムで実装される外部関数) の宣言が追加されました。typesByString()
関数が追加され、文字列に基づいて既存の型を検索するロジックが実装されました。lookupCache
構造体と、cacheGet
,cachePut
ヘルパー関数が追加され、動的に生成される型のキャッシュ機構が導入されました。ChanOf
,MapOf
,SliceOf
,arrayOf
(非公開のArrayOf
の実体) の各関数が実装され、動的な型生成ロジックが記述されました。これらはキャッシュの利用、既存型の検索、および必要に応じた新しい型の構築を行います。fnv1
ハッシュ関数が追加されました。
-
src/pkg/reflect/value.go
:Value
構造体のtyp
フィールドが*commonType
から*rtype
に変更されました。emptyInterface
およびnonEmptyInterface
構造体のtyp
フィールドも*runtimeType
から*rtype
に変更されました。reflect
パッケージ内の様々な関数(Elem
,Field
,Index
,MapIndex
,MapKeys
,Send
,Recv
,SetMapIndex
,Slice
,Type
,Select
,MakeSlice
,MakeChan
,MakeMap
,ValueOf
,Zero
,New
,NewAt
,assignTo
,Convert
など)が、型情報を*rtype
として直接扱うように修正されました。
-
src/pkg/runtime/type.go
:- ランタイム側の
commonType
構造体がrtype
にリネームされ、ptrToThis
フィールドが*interface{}
から*rtype
に変更されました。 - メソッドの型 (
mtyp
,typ
) も*interface{}
から*rtype
に変更されました。
- ランタイム側の
-
src/pkg/runtime/iface.c
:reflect·unsafe_New
およびreflect·unsafe_NewArray
関数が、Eface typ
の代わりにType *t
(Goの*rtype
に対応) を直接引数として受け取るように変更されました。これにより、Goのreflect
パッケージから渡される型情報を直接利用できるようになりました。reflect·typelinks
関数が追加されました。このC関数は、リンカによって生成された.typelink
セクションの開始と終了アドレスを利用して、Goのreflect
パッケージに型情報のスライスを返します。
-
src/cmd/gc/reflect.c
:typestruct
関数(interface{}
ヘッダに関連するロジック)が削除されました。typelinksym
関数が追加され、go.typelink.
で始まるシンボル名を生成するようになりました。dtypesym
関数内で、配列、チャネル、マップといった複合型に対して、対応するtypelink
シンボルを生成し、リンカが収集できるようにするロジックが追加されました。
-
src/cmd/ld/data.c
およびsrc/cmd/ld/symtab.c
:src/cmd/ld/lib.h
でSTYPELINK
という新しいシンボルタイプが定義されました。data.c
で、バイナリ内に.typelink
という新しい読み取り専用セクションが追加され、STYPELINK
タイプのシンボルがこのセクションに配置されるようになりました。symtab.c
で、go.typelink.
で始まるシンボルがSTYPELINK
タイプとして認識されるように設定されました。
これらの変更は、Goの型システム、リフレクション、およびバイナリ生成の各層にわたる、密接に連携した大規模な改修を示しています。
コアとなるコードの解説
このコミットの核となる変更は、Goの型システムが実行時にどのように表現され、操作されるかという点にあります。
1. rtype
への移行とメモリ効率
以前のGoのランタイムでは、reflect
パッケージが扱う型情報は commonType
という構造体で表現されていました。この commonType
は、runtimeType interface{}
というフィールドを持っていました。Goのインターフェースは、内部的に「型情報へのポインタ」と「値へのポインタ」の2つのワードで構成されます。したがって、runtimeType interface{}
は、型情報自体を指すポインタを保持するために、さらに2ワードのオーバーヘッドを持っていました。
このコミットでは、この commonType
が rtype
にリネームされ、runtimeType interface{}
フィールドが削除されました。代わりに、ptrToThis *rtype
のように、直接 *rtype
へのポインタを持つようになりました。これにより、各型情報から2ワードのメモリが削減されます。これは、Goのバイナリに含まれる数千から数万の型情報全体で考えると、かなりのメモリ削減(例として godoc
バイナリで156KB)に繋がります。
この変更は、reflect
パッケージ内のすべての型構造体(arrayType
, chanType
, mapType
, sliceType
など)に波及し、これらが埋め込む commonType
が rtype
に変更されました。これにより、reflect
パッケージ全体がより効率的な型表現を利用するようになります。
2. 動的な型生成関数 (ChanOf
, MapOf
, SliceOf
, ArrayOf
)
これらの関数は、実行時に新しい複合型を生成または取得するための主要なAPIです。
-
キャッシュの利用:
ChanOf
,MapOf
,SliceOf
,ArrayOf
は、まずlookupCache
というグローバルなキャッシュをチェックします。これはsync.RWMutex
で保護されたマップで、cacheKey
(型の種類、サブタイプ、追加情報を含む) をキーとして*rtype
を格納します。これにより、同じ型が複数回要求された場合に、計算コストの高い型生成処理をスキップし、既存の型を再利用できます。 -
既存型の検索 (
typelinks
とtypesByString
): キャッシュに型が存在しない場合、これらの関数はバイナリ内に既に存在する型の中から、要求された構造に合致する型を検索しようとします。typelinks()
: これはsrc/pkg/runtime/iface.c
で実装されているGoのランタイム関数で、リンカによってバイナリに埋め込まれた.typelink
セクションから、すべての既知の複合型(配列、チャネル、マップ、スライス)の*rtype
ポインタのリストを返します。typesByString(s string) []*rtype
:typelinks()
が返すリストの中から、指定された文字列表現s
に一致する型を効率的に検索します。Goの型は文字列表現が一意ではない場合があるため、この関数は一致するすべての型をスライスとして返します。
-
新しい型の構築: 既存の型が見つからない場合、
reflect
パッケージはunsafe
パッケージを駆使して、新しい型構造体(例:chanType
,mapType
)をメモリ上に動的に構築します。- 例えば、
ChanOf
の実装では、まず(chan unsafe.Pointer)(nil)
のような既存のチャネル型のプロトタイプをunsafe.Pointer
を使ってコピーします。 - 次に、新しいチャネルの要素型 (
elem
) や方向 (dir
)、文字列表現 (string
)、ハッシュ値 (hash
) などのフィールドを上書きします。ハッシュ値はfnv1
関数を使って計算され、型の同一性チェックに利用されます。 - 構築された新しい
*rtype
はキャッシュに格納され、呼び出し元に返されます。
- 例えば、
3. リンカとランタイムによる型情報の公開 (.typelink
セクション)
動的に型を生成するだけでなく、既存の型を効率的に検索・再利用できるようにするために、リンカとランタイムの連携が強化されました。
-
コンパイラ (
src/cmd/gc/reflect.c
) の役割: コンパイラは、Goのソースコードをコンパイルする際に、配列、チャネル、マップ、スライスといった複合型が定義されると、その型に対応するgo.typelink.
で始まる特殊なシンボルを生成します。これらのシンボルは、実際の型情報 (*rtype
) へのポインタを保持します。 -
リンカ (
src/cmd/ld/data.c
,src/cmd/ld/symtab.c
) の役割:- リンカは、コンパイラが生成したこれらの
go.typelink.
シンボルを認識し、これらをバイナリ内の新しい読み取り専用セクションである.typelink
に配置します。 .typelink
セクションは、バイナリ内のすべての動的に検索可能な複合型へのポインタのリストとして機能します。これにより、reflect
パッケージは、バイナリ全体をスキャンすることなく、必要な型情報に効率的にアクセスできるようになります。
- リンカは、コンパイラが生成したこれらの
-
ランタイム (
src/pkg/runtime/iface.c
) の役割:- ランタイムには
reflect·typelinks
というC関数が追加されました。この関数は、リンカによって設定された.typelink
セクションの開始アドレス (typelink[]
) と終了アドレス (etypelink[]
) を利用して、そのセクション全体をGoの[]*rtype
スライスとしてreflect
パッケージに公開します。 reflect
パッケージのtypelinks()
Go関数は、このランタイムのC関数を呼び出すことで、バイナリ内のすべての既知の複合型へのポインタのリストを取得します。
- ランタイムには
この一連のメカニズムにより、reflect
パッケージは、動的に新しい型を生成するだけでなく、既にバイナリに存在する型を効率的に発見し、再利用することが可能になります。これは、メモリ使用量の最適化と、リフレクション機能の柔軟性の両立を実現するための重要な設計です。
関連リンク
- Go Issue 2339: reflect: add ArrayOf, ChanOf, MapOf, SliceOf
- Go Issue 4375: reflect: ArrayOf needs correct alg field
- Go CL 6572043: reflect: add ArrayOf, ChanOf, MapOf, SliceOf
参考にした情報源リンク
- Go Programming Language Specification - The reflect package:
- Go Data Structures - The Go Programming Language (Russ Cox):
- A Guide to the Go Compiler (Go Team):
- Go Linker Internals (Eli Bendersky):
- The Go runtime type information (Eli Bendersky):
- FNV hash function:
- Go's
unsafe
package documentation: - Go's
interface{}
internal representation: - Go's
reflect
package source code (relevant files from the commit):src/pkg/reflect/type.go
src/pkg/reflect/value.go
src/pkg/runtime/type.go
src/pkg/runtime/iface.c
src/cmd/gc/reflect.c
src/cmd/ld/data.c
src/cmd/ld/symtab.c