[インデックス 1832] ファイルの概要
このコミットは、Go言語のランタイムにおけるインターフェースの内部表現、特にSigt
(型シグネチャ)とSigi
(インターフェースシグネチャ)のデータ構造のフォーマットを変更するものです。これにより、型ハッシュのための領域が確保され、実行時における型スイッチ(型アサーションや型変換)の効率化が図られています。具体的には、インターフェースの型情報にハッシュ値を含めることで、型の一致判定を高速化し、インターフェースの動的な振る舞いを最適化することを目的としています。
コミット
commit 5136a9e1f7ed5fbeaa7d48641ae8c28c513727ae
Author: Ken Thompson <ken@golang.org>
Date: Mon Mar 16 15:27:08 2009 -0700
change format of Sigt and Sigi
to allow room for type hash
needed for log-time type switch.
R=r
OCL=26354
CL=26354
---
src/cmd/6g/obj.c | 82 ++++++++++++++++----------
src/cmd/gc/subr.c | 5 --
src/runtime/iface.c | 167 ++++++++++++++++++++++++++++++----------------------
3 files changed, 147 insertions(+), 107 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5136a9e1f7ed5fbeaa7d48641ae8c28c513727ae
元コミット内容
Sigt
とSigi
のフォーマットを変更し、型ハッシュのための領域を確保する。これは、ログタイム(対数時間)での型スイッチに必要となる。
変更の背景
Go言語のインターフェースは、静的型付け言語でありながら動的なポリモーフィズムを実現するための強力な機能です。インターフェース変数は、そのインターフェースを満たす任意の具象型の値を保持できます。実行時に、インターフェース変数が実際にどの具象型を保持しているかを判断し、それに応じた処理を行う「型スイッチ」や「型アサーション」が頻繁に行われます。
このコミットが行われた2009年3月は、Go言語がまだ公開される前の初期開発段階でした。当時のインターフェースの実装では、型スイッチや型アサーションの際に、具象型とインターフェース型が持つメソッドセットを比較するなど、比較的コストのかかる処理が行われていた可能性があります。
このコミットの目的は、「log-time type switch」(対数時間での型スイッチ)を実現することです。これは、型スイッチのパフォーマンスを向上させるための重要な最適化であり、そのためには型情報にハッシュ値を含めることが必要とされました。ハッシュ値を用いることで、複雑な型比較を、単純なハッシュ値の比較に置き換えることができ、処理時間を大幅に短縮することが可能になります。
前提知識の解説
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。具象型がインターフェースのすべてのメソッドを実装していれば、その具象型は自動的にそのインターフェースを満たします。インターフェース変数は、具象型の値と、その具象型の型情報を内部的に保持しています。
インターフェースの内部構造(変更前)
Go言語のインターフェースは、内部的には通常、2つのポインタで構成されています。
- データポインタ: 具象型の値へのポインタ。
- 型情報ポインタ: 具象型の型情報(メソッドセット、サイズ、アライメントなど)を記述した構造体へのポインタ。
この型情報ポインタが指す構造体が、このコミットで変更されるSigt
(Type Signature)とSigi
(Interface Signature)に関連します。
Sigt
(Type Signature): 具象型(構造体など)の型情報を表す構造体です。その型が実装するメソッドのリストや、型のサイズ、アライメントなどの情報を含みます。Sigi
(Interface Signature): インターフェース型自体の情報を表す構造体です。インターフェースが要求するメソッドのリストを含みます。
インターフェースの型アサーションや型スイッチでは、インターフェース変数が保持する具象型のSigt
情報と、ターゲットとなるインターフェース型または具象型のSigi
/Sigt
情報を比較し、互換性があるか、あるいは一致するかを判断します。
型ハッシュ (Type Hash)
型ハッシュとは、Goの型定義を一意に識別するためのハッシュ値です。型の構造(フィールド、メソッド、基底型など)に基づいて計算されます。このハッシュ値を用いることで、実行時に型の比較を行う際に、複雑な構造全体を比較する代わりに、ハッシュ値を比較するだけで高速に同等性を判定できるようになります。これにより、特にインターフェースの型アサーションや型スイッチの性能が向上します。
対数時間での型スイッチ (Log-time Type Switch)
型スイッチや型アサーションの操作は、インターフェースが保持する具象型が、指定された型と互換性があるか、あるいは一致するかをチェックするものです。 「対数時間での型スイッチ」とは、このチェックの計算量が、型の複雑さやメソッドの数に対して対数的に増加する、あるいは定数時間に近い形で処理できることを意味します。ハッシュ値を用いることで、線形探索や複雑な比較を避け、ハッシュテーブルのようなデータ構造を利用して高速に型を検索・比較することが可能になります。これにより、大規模なプログラムや、多数の型が関与する場面でのパフォーマンスが向上します。
技術的詳細
このコミットの主要な変更点は、Goランタイムがインターフェースの型情報を表現するために使用するSigt
とSigi
構造体のレイアウト変更です。
Sigt
構造体の変更 (src/runtime/iface.c
)
変更前:
struct Sigt
{
byte* name;
uint32 hash; // hash of type // first is alg
uint32 offset; // offset of substruct // first is width
void (*fun)(void);
};
変更後:
struct Sigt
{
byte* name; // name of basic type
Sigt* link; // for linking into hash tables
uint32 thash; // hash of type
uint32 mhash; // hash of methods
uint16 width; // width of base type in bytes
uint16 alg; // algorithm
uint32 pad;
struct {
byte* fname;
uint32 fhash; // hash of type
uint32 offset; // offset of substruct
void (*fun)(void);
} meth[1]; // one or more - last name is nil
};
主な変更点:
hash
とoffset
フィールドが削除され、より詳細なフィールドが追加されました。thash
(type hash): 型全体のハッシュ値。mhash
(method hash): 型が実装するメソッドのハッシュ値。width
: 型のバイト単位の幅(サイズ)。alg
: 型の比較やハッシュ計算に使用されるアルゴリズムの識別子。link
: ハッシュテーブルにリンクするためのポインタ。pad
: アライメントのためのパディング。
meth
フィールドが、メソッド名(fname
)、メソッドのハッシュ値(fhash
)、オフセット(offset
)、関数ポインタ(fun
)を含む構造体の配列として明示的に定義されました。これにより、各メソッドのハッシュ値が個別に管理できるようになります。
Sigi
構造体の変更 (src/runtime/iface.c
)
変更前:
struct Sigi
{
byte* name;
uint32 hash;
uint32 perm; // location of fun in Sigt // first is size
};
変更後:
struct Sigi
{
byte* name;
uint32 hash;
uint32 size; // number of methods
struct {
byte* fname;
uint32 fhash;
uint32 perm; // location of fun in Sigt
} meth[1]; // [size+1] - last name is nil
};
主な変更点:
perm
フィールドが削除され、size
フィールド(メソッドの数)が追加されました。meth
フィールドが、メソッド名(fname
)、メソッドのハッシュ値(fhash
)、perm
(Sigt
内の関数位置)を含む構造体の配列として明示的に定義されました。
コンパイラ (src/cmd/6g/obj.c
) の変更
Goコンパイラのバックエンド(当時の6g
)は、Goソースコードをコンパイルする際に、これらのSigt
やSigi
構造体を生成します。このコミットでは、dumpsigt
関数とdumpsigi
関数が、新しい構造体レイアウトに合わせて、型情報やメソッド情報をどのようにメモリに配置するかを変更しています。
特に、dumpsigt
では、progt
(具象型)のtypehash
とsighash
(メソッドのハッシュ)を生成し、これらを新しいSigt
構造体のthash
とmhash
フィールドに書き込む処理が追加されています。また、型の幅(width
)とアルゴリズム(alg
)も明示的に書き込まれるようになりました。
ランタイム (src/runtime/iface.c
) の変更
src/runtime/iface.c
では、インターフェースの動的な振る舞いを司る様々な関数が、新しいSigt
とSigi
の構造体定義に合わせて修正されています。
printsigi
、printsigt
: デバッグ出力関数が新しいフィールドを参照するように変更。itype
: インターフェース型と具象型の互換性をチェックし、Itype
(インターフェースの実行時型情報)を生成する関数。ハッシュ値(si->thash
、st->mhash
)を利用して、ハッシュテーブルからの検索を高速化しています。sys·ifaceT2I
(Type to Interface)、sys·ifaceI2T
(Interface to Type)、sys·ifaceI2T2
(Interface to Type with success boolean): 型変換を行うこれらの関数が、Sigt
のwidth
とalg
フィールドを直接参照するように変更されました。これにより、型のサイズやコピーアルゴリズムの取得がより直接的になります。ifacehash
、ifaceeq
: インターフェースのハッシュ値を計算したり、インターフェース同士を比較したりする関数も、新しいSigt
構造体のalg
とwidth
フィールドを利用するように変更されました。fakesigt
: 偽のSigt
を生成する関数も、新しい構造体に合わせて更新されています。特に、ハッシュテーブルへのリンクにsigt->link
を使用するようになりました。
型ハッシュ計算 (src/cmd/gc/subr.c
) の変更
typehash
関数から、再帰検出のためのrecur
フラグの処理が削除されました。これは、型ハッシュの計算ロジックが改善され、再帰的な型定義のハッシュ計算がより堅牢になったか、あるいはハッシュ計算のフェーズが変更されたことを示唆しています。
これらの変更により、Goランタイムはインターフェースの型情報をより効率的に管理し、特に型アサーションや型スイッチといった実行時操作のパフォーマンスを大幅に向上させることが可能になりました。ハッシュ値の導入は、Goのインターフェースが持つ動的な性質を、静的型付けの安全性と実行時の効率性を両立させる上で重要な一歩でした。
コアとなるコードの変更箇所
src/runtime/iface.c
における Sigt
構造体の定義変更
--- a/src/runtime/iface.c
+++ b/src/runtime/iface.c
@@ -15,17 +15,31 @@ typedef struct Itype Itype;
*/
struct Sigt
{
- byte* name;
- uint32 hash; // hash of type // first is alg
- uint32 offset; // offset of substruct // first is width
- void (*fun)(void);
+ byte* name; // name of basic type
+ Sigt* link; // for linking into hash tables
+ uint32 thash; // hash of type
+ uint32 mhash; // hash of methods
+ uint16 width; // width of base type in bytes
+ uint16 alg; // algorithm
+ uint32 pad;
+ struct {
+ byte* fname;
+ uint32 fhash; // hash of type
+ uint32 offset; // offset of substruct
+ void (*fun)(void);
+ } meth[1]; // one or more - last name is nil
};
src/runtime/iface.c
における Sigi
構造体の定義変更
--- a/src/runtime/iface.c
+++ b/src/runtime/iface.c
@@ -29,9 +29,13 @@ struct Sigt
struct Sigi
{
byte* name;
uint32 hash;
- uint32 perm; // location of fun in Sigt // first is size
+ uint32 size; // number of methods
+ struct {
+ byte* fname;
+ uint32 fhash;
+ uint32 perm; // location of fun in Sigt
+ } meth[1]; // [size+1] - last name is nil
};
src/cmd/6g/obj.c
における dumpsigt
関数の変更(抜粋)
dumpsigt
関数内で、新しいSigt
構造体のフィールドに値を設定する部分が変更されています。
--- a/src/cmd/6g/obj.c
+++ b/src/cmd/6g/obj.c
@@ -713,38 +732,23 @@ dumpsigt(Type *progt, Type *ifacet, Type *rcvrt, Type *methodt, Sym *s)
\tot = 0;
\tot = rnd(ot, maxround); // base structure
-\t// sigt[0].name = ""
-\tginsatoa(widthptr, stringo);
+\t// base of type signature contains parameters
+\tginsatoa(widthptr, stringo); // name
+\tot = rnd(ot, widthptr)+widthptr; // skip link
+\tgensatac(wi, typehash(progt, 0)); // thash
+\tgensatac(wi, sighash); // mhash
+\tgensatac(ws, progt->width); // width
+\tgensatac(ws, algtype(progt)); // algorithm
-\t// save type name for runtime error message.
\tsnprint(buf, sizeof buf, "%#T", progt);
\tdatastring(buf, strlen(buf)+1);
-\t// first field of an type signature contains
-\t// the element parameters and is not a real entry
-\t// sigt[0].hash = elemalg + sighash<<8
-\tgensatac(wi, algtype(progt) + (sighash<<8));
-\
-\t// sigt[0].offset = width
-\tgensatac(wi, progt->width);
-\
-\t// skip the function
-\tgensatac(widthptr, 0);
-\
\tfor(b=a; b!=nil; b=b->link) {
-\t\tot = rnd(ot, maxround); // base structure
-\
-\t\t// sigt[++].name = "fieldname"
-\t\tginsatoa(widthptr, stringo);
-\
-\t\t// sigt[++].hash = hashcode
-\t\tgensatac(wi, b->hash);
-\
-\t\t// sigt[++].offset = of embedded struct
-\t\tgensatac(wi, 0);
-\
-\t\t// sigt[++].fun = &method
-\t\tgensatad(b->sym);
+\t\tot = rnd(ot, maxround); // base of substructure
+\t\tginsatoa(widthptr, stringo); // field name
+\t\tgensatac(wi, b->hash); // hash
+\tgensatac(wi, 0); // offset
+\t\tgensatad(b->sym); // &method
\t\tdatastring(b->name, strlen(b->name)+1);
\t}
コアとなるコードの解説
Sigt
とSigi
の構造体変更
この変更の核心は、Sigt
とSigi
という2つの重要なランタイムデータ構造に、型ハッシュとメソッドハッシュのための専用フィールドが追加されたことです。
-
Sigt
(Type Signature): 具象型の実行時表現です。thash
(type hash): 具象型全体のハッシュ値。型の構造(フィールド、メソッド、基底型など)から計算されます。これにより、型アサーションや型スイッチの際に、具象型の一致を高速に判定できます。mhash
(method hash): その具象型が実装するメソッド群のハッシュ値。インターフェースのメソッドセットとの互換性チェックを高速化するために使用されます。width
: 型のメモリ上でのサイズ(バイト単位)。alg
: 型のコピー、比較、ハッシュ計算などに使用されるアルゴリズムを識別する値。これにより、型に応じた適切な操作を効率的に選択できます。link
: ハッシュテーブルにSigt
エントリをリンクするためのポインタ。これにより、Sigt
の検索が高速化されます。meth
配列: 各メソッドの詳細(名前、ハッシュ、オフセット、関数ポインタ)を格納します。特にfhash
(フィールドハッシュ、ここではメソッドハッシュ)が追加され、個々のメソッドの一致判定も高速化されます。
-
Sigi
(Interface Signature): インターフェース型の実行時表現です。size
: インターフェースが要求するメソッドの数。meth
配列: インターフェースが要求する各メソッドの詳細(名前、ハッシュ、Sigt
内の関数位置)を格納します。ここでもfhash
が追加され、インターフェースが要求するメソッドと具象型が提供するメソッドのマッチングを高速化します。
コンパイラ (src/cmd/6g/obj.c
) の役割
src/cmd/6g/obj.c
のdumpsigt
関数は、コンパイル時にGoの型情報からSigt
構造体を生成し、実行可能ファイルに埋め込む役割を担っています。
変更前は、Sigt
の最初の要素に型全体のハッシュと幅を詰め込んでいましたが、変更後はthash
、mhash
、width
、alg
といった専用のフィールドにそれぞれ適切な値を書き込むようになりました。
typehash(progt, 0)
は具象型progt
のハッシュ値を計算し、sighash
はメソッドのハッシュ値を計算します。これらがそれぞれthash
とmhash
に格納されます。
この変更により、コンパイラが生成する型情報がより構造化され、ランタイムでの利用が容易かつ効率的になります。
ランタイム (src/runtime/iface.c
) の役割
src/runtime/iface.c
は、Goプログラムの実行時にインターフェースの動的な振る舞いを管理するGoランタイムの重要な部分です。
itype
関数: インターフェースの型アサーションや型変換の際に呼び出されます。この関数は、インターフェースが保持する具象型のSigt
と、ターゲットとなるインターフェースのSigi
を比較し、互換性のあるItype
(インターフェースの実行時型情報)を生成または検索します。変更後、itype
はSigt
のthash
とmhash
を利用して、ハッシュテーブルから既存のItype
を高速に検索できるようになりました。これにより、毎回メソッドの比較を行う必要がなくなり、パフォーマンスが向上します。sys·ifaceT2I
、sys·ifaceI2T
、sys·ifaceI2T2
: これらの関数は、型とインターフェース間の値の変換を処理します。変更後、Sigt
のwidth
とalg
フィールドを直接参照することで、値のコピーやメモリ割り当てのロジックが簡素化され、効率が向上しました。ifacehash
、ifaceeq
: インターフェースの値をハッシュしたり、比較したりする際に使用されます。これらの関数も、Sigt
のalg
とwidth
フィールドを利用して、型に応じた適切なハッシュ関数や比較関数を呼び出すようになりました。これにより、インターフェースをキーとするマップ操作などが高速化されます。
これらの変更は、Go言語のインターフェースが、その柔軟性を保ちつつも、実行時のオーバーヘッドを最小限に抑えるための初期の重要な最適化ステップであったと言えます。ハッシュ値の導入により、複雑な型比較が高速なハッシュ比較に置き換えられ、Goプログラム全体のパフォーマンス向上に寄与しました。
関連リンク
- Go言語のインターフェースに関する公式ドキュメントやチュートリアル(当時のものを見つけるのは難しいですが、現在のGoのインターフェースの概念は当時から大きく変わっていません)。
- Go言語のランタイムソースコード(特に
src/runtime/iface.go
やsrc/runtime/type.go
など、現在のインターフェースや型情報の定義)。
参考にした情報源リンク
- Go言語のソースコード(特にコミット履歴と関連ファイル)
src/cmd/6g/obj.c
src/cmd/gc/subr.c
src/runtime/iface.c
- Go言語のインターフェースに関する一般的な解説記事やドキュメント。
- ハッシュテーブル、ハッシュ関数に関する一般的なコンピュータサイエンスの知識。
- Go言語の初期開発に関する情報(Goの歴史、設計思想など)。
- The Go Programming Language (go.dev)
- Go at Google: Language Design in the Service of Software Engineering (research.google)```
[インデックス 1832] ファイルの概要
このコミットは、Go言語のランタイムにおけるインターフェースの内部表現、特にSigt
(型シグネチャ)とSigi
(インターフェースシグネチャ)のデータ構造のフォーマットを変更するものです。これにより、型ハッシュのための領域が確保され、実行時における型スイッチ(型アサーションや型変換)の効率化が図られています。具体的には、インターフェースの型情報にハッシュ値を含めることで、型の一致判定を高速化し、インターフェースの動的な振る舞いを最適化することを目的としています。
コミット
commit 5136a9e1f7ed5fbeaa7d48641ae8c28c513727ae
Author: Ken Thompson <ken@golang.org>
Date: Mon Mar 16 15:27:08 2009 -0700
change format of Sigt and Sigi
to allow room for type hash
needed for log-time type switch.
R=r
OCL=26354
CL=26354
---
src/cmd/6g/obj.c | 82 ++++++++++++++++----------
src/cmd/gc/subr.c | 5 --
src/runtime/iface.c | 167 ++++++++++++++++++++++++++++++----------------------
3 files changed, 147 insertions(+), 107 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5136a9e1f7ed5fbeaa7d48641ae8c28c513727ae
元コミット内容
Sigt
とSigi
のフォーマットを変更し、型ハッシュのための領域を確保する。これは、ログタイム(対数時間)での型スイッチに必要となる。
変更の背景
Go言語のインターフェースは、静的型付け言語でありながら動的なポリモーフィズムを実現するための強力な機能です。インターフェース変数は、そのインターフェースを満たす任意の具象型の値を保持できます。実行時に、インターフェース変数が実際にどの具象型を保持しているかを判断し、それに応じた処理を行う「型スイッチ」や「型アサーション」が頻繁に行われます。
このコミットが行われた2009年3月は、Go言語がまだ公開される前の初期開発段階でした。当時のインターフェースの実装では、型スイッチや型アサーションの際に、具象型とインターフェース型が持つメソッドセットを比較するなど、比較的コストのかかる処理が行われていた可能性があります。
このコミットの目的は、「log-time type switch」(対数時間での型スイッチ)を実現することです。これは、型スイッチのパフォーマンスを向上させるための重要な最適化であり、そのためには型情報にハッシュ値を含めることが必要とされました。ハッシュ値を用いることで、複雑な型比較を、単純なハッシュ値の比較に置き換えることができ、処理時間を大幅に短縮することが可能になります。
前提知識の解説
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。具象型がインターフェースのすべてのメソッドを実装していれば、その具象型は自動的にそのインターフェースを満たします。インターフェース変数は、具象型の値と、その具象型の型情報を内部的に保持しています。
インターフェースの内部構造(変更前)
Go言語のインターフェースは、内部的には通常、2つのポインタで構成されています。
- データポインタ: 具象型の値へのポインタ。
- 型情報ポインタ: 具象型の型情報(メソッドセット、サイズ、アライメントなど)を記述した構造体へのポインタ。
この型情報ポインタが指す構造体が、このコミットで変更されるSigt
(Type Signature)とSigi
(Interface Signature)に関連します。
Sigt
(Type Signature): 具象型(構造体など)の型情報を表す構造体です。その型が実装するメソッドのリストや、型のサイズ、アライメントなどの情報を含みます。Sigi
(Interface Signature): インターフェース型自体の情報を表す構造体です。インターフェースが要求するメソッドのリストを含みます。
インターフェースの型アサーションや型スイッチでは、インターフェース変数が保持する具象型のSigt
情報と、ターゲットとなるインターフェース型または具象型のSigi
/Sigt
情報を比較し、互換性があるか、あるいは一致するかを判断します。
型ハッシュ (Type Hash)
型ハッシュとは、Goの型定義を一意に識別するためのハッシュ値です。型の構造(フィールド、メソッド、基底型など)に基づいて計算されます。このハッシュ値を用いることで、実行時に型の比較を行う際に、複雑な構造全体を比較する代わりに、ハッシュ値を比較するだけで高速に同等性を判定できるようになります。これにより、特にインターフェースの型アサーションや型スイッチの性能が向上します。
対数時間での型スイッチ (Log-time Type Switch)
型スイッチや型アサーションの操作は、インターフェースが保持する具象型が、指定された型と互換性があるか、あるいは一致するかをチェックするものです。 「対数時間での型スイッチ」とは、このチェックの計算量が、型の複雑さやメソッドの数に対して対数的に増加する、あるいは定数時間に近い形で処理できることを意味します。ハッシュ値を用いることで、線形探索や複雑な比較を避け、ハッシュテーブルのようなデータ構造を利用して高速に型を検索・比較することが可能になります。これにより、大規模なプログラムや、多数の型が関与する場面でのパフォーマンスが向上します。
技術的詳細
このコミットの主要な変更点は、Goランタイムがインターフェースの型情報を表現するために使用するSigt
とSigi
構造体のレイアウト変更です。
Sigt
構造体の変更 (src/runtime/iface.c
)
変更前:
struct Sigt
{
byte* name;
uint32 hash; // hash of type // first is alg
uint32 offset; // offset of substruct // first is width
void (*fun)(void);
};
変更後:
struct Sigt
{
byte* name; // name of basic type
Sigt* link; // for linking into hash tables
uint32 thash; // hash of type
uint32 mhash; // hash of methods
uint16 width; // width of base type in bytes
uint16 alg; // algorithm
uint32 pad;
struct {
byte* fname;
uint32 fhash; // hash of type
uint32 offset; // offset of substruct
void (*fun)(void);
} meth[1]; // one or more - last name is nil
};
主な変更点:
hash
とoffset
フィールドが削除され、より詳細なフィールドが追加されました。thash
(type hash): 型全体のハッシュ値。mhash
(method hash): 型が実装するメソッドのハッシュ値。width
: 型のバイト単位の幅(サイズ)。alg
: 型の比較やハッシュ計算に使用されるアルゴリズムの識別子。link
: ハッシュテーブルにリンクするためのポインタ。pad
: アライメントのためのパディング。
meth
フィールドが、メソッド名(fname
)、メソッドのハッシュ値(fhash
)、オフセット(offset
)、関数ポインタ(fun
)を含む構造体の配列として明示的に定義されました。これにより、各メソッドのハッシュ値が個別に管理できるようになります。
Sigi
構造体の変更 (src/runtime/iface.c
)
変更前:
struct Sigi
{
byte* name;
uint32 hash;
uint32 perm; // location of fun in Sigt // first is size
};
変更後:
struct Sigi
{
byte* name;
uint32 hash;
uint32 size; // number of methods
struct {
byte* fname;
uint32 fhash;
uint32 perm; // location of fun in Sigt
} meth[1]; // [size+1] - last name is nil
};
主な変更点:
perm
フィールドが削除され、size
フィールド(メソッドの数)が追加されました。meth
フィールドが、メソッド名(fname
)、メソッドのハッシュ値(fhash
)、perm
(Sigt
内の関数位置)を含む構造体の配列として明示的に定義されました。
コンパイラ (src/cmd/6g/obj.c
) の変更
Goコンパイラのバックエンド(当時の6g
)は、Goソースコードをコンパイルする際に、これらのSigt
やSigi
構造体を生成します。このコミットでは、dumpsigt
関数とdumpsigi
関数が、新しい構造体レイアウトに合わせて、型情報やメソッド情報をどのようにメモリに配置するかを変更しています。
特に、dumpsigt
では、progt
(具象型)のtypehash
とsighash
(メソッドのハッシュ)を生成し、これらを新しいSigt
構造体のthash
とmhash
フィールドに書き込む処理が追加されています。また、型の幅(width
)とアルゴリズム(alg
)も明示的に書き込まれるようになりました。
ランタイム (src/runtime/iface.c
) の変更
src/runtime/iface.c
では、インターフェースの動的な振る舞いを司る様々な関数が、新しいSigt
とSigi
の構造体定義に合わせて修正されています。
printsigi
、printsigt
: デバッグ出力関数が新しいフィールドを参照するように変更。itype
: インターフェース型と具象型の互換性をチェックし、Itype
(インターフェースの実行時型情報)を生成する関数。ハッシュ値(si->thash
、st->mhash
)を利用して、ハッシュテーブルからの検索を高速化しています。sys·ifaceT2I
(Type to Interface)、sys·ifaceI2T
(Interface to Type)、sys·ifaceI2T2
(Interface to Type with success boolean): 型変換を行うこれらの関数が、Sigt
のwidth
とalg
フィールドを直接参照するように変更されました。これにより、型のサイズやコピーアルゴリズムの取得がより直接的になります。ifacehash
、ifaceeq
: インターフェースのハッシュ値を計算したり、インターフェース同士を比較したりする関数も、新しいSigt
構造体のalg
とwidth
フィールドを利用するように変更されました。fakesigt
: 偽のSigt
を生成する関数も、新しい構造体に合わせて更新されています。特に、ハッシュテーブルへのリンクにsigt->link
を使用するようになりました。
型ハッシュ計算 (src/cmd/gc/subr.c
) の変更
typehash
関数から、再帰検出のためのrecur
フラグの処理が削除されました。これは、型ハッシュの計算ロジックが改善され、再帰的な型定義のハッシュ計算がより堅牢になったか、あるいはハッシュ計算のフェーズが変更されたことを示唆しています。
これらの変更により、Goランタイムはインターフェースの型情報をより効率的に管理し、特に型アサーションや型スイッチといった実行時操作のパフォーマンスを大幅に向上させることが可能になりました。ハッシュ値の導入は、Goのインターフェースが持つ動的な性質を、静的型付けの安全性と実行時の効率性を両立させる上で重要な一歩でした。
コアとなるコードの変更箇所
src/runtime/iface.c
における Sigt
構造体の定義変更
--- a/src/runtime/iface.c
+++ b/src/runtime/iface.c
@@ -15,17 +15,31 @@ typedef struct Itype Itype;
*/
struct Sigt
{
- byte* name;
- uint32 hash; // hash of type // first is alg
- uint32 offset; // offset of substruct // first is width
- void (*fun)(void);
+ byte* name; // name of basic type
+ Sigt* link; // for linking into hash tables
+ uint32 thash; // hash of type
+ uint32 mhash; // hash of methods
+ uint16 width; // width of base type in bytes
+ uint16 alg; // algorithm
+ uint32 pad;
+ struct {
+ byte* fname;
+ uint32 fhash; // hash of type
+ uint32 offset; // offset of substruct
+ void (*fun)(void);
+ } meth[1]; // one or more - last name is nil
};
src/runtime/iface.c
における Sigi
構造体の定義変更
--- a/src/runtime/iface.c
+++ b/src/runtime/iface.c
@@ -29,9 +29,13 @@ struct Sigt
struct Sigi
{
byte* name;
uint32 hash;
- uint32 perm; // location of fun in Sigt // first is size
+ uint32 size; // number of methods
+ struct {
+ byte* fname;
+ uint32 fhash;
+ uint32 perm; // location of fun in Sigt
+ } meth[1]; // [size+1] - last name is nil
};
src/cmd/6g/obj.c
における dumpsigt
関数の変更(抜粋)
dumpsigt
関数内で、新しいSigt
構造体のフィールドに値を設定する部分が変更されています。
--- a/src/cmd/6g/obj.c
+++ b/src/cmd/6g/obj.c
@@ -713,38 +732,23 @@ dumpsigt(Type *progt, Type *ifacet, Type *rcvrt, Type *methodt, Sym *s)
\tot = 0;
\tot = rnd(ot, maxround); // base structure
-\t// sigt[0].name = ""
-\tginsatoa(widthptr, stringo);
+\t// base of type signature contains parameters
+\tginsatoa(widthptr, stringo); // name
+\tot = rnd(ot, widthptr)+widthptr; // skip link
+\tgensatac(wi, typehash(progt, 0)); // thash
+\tgensatac(wi, sighash); // mhash
+\tgensatac(ws, progt->width); // width
+\tgensatac(ws, algtype(progt)); // algorithm
-\t// save type name for runtime error message.
\tsnprint(buf, sizeof buf, "%#T", progt);
\tdatastring(buf, strlen(buf)+1);
-\t// first field of an type signature contains
-\t// the element parameters and is not a real entry
-\t// sigt[0].hash = elemalg + sighash<<8
-\tgensatac(wi, algtype(progt) + (sighash<<8));
-\
-\t// sigt[0].offset = width
-\tgensatac(wi, progt->width);
-\
-\t// skip the function
-\tgensatac(widthptr, 0);
-\
\tfor(b=a; b!=nil; b=b->link) {
-\t\tot = rnd(ot, maxround); // base structure
-\
-\t\t// sigt[++].name = "fieldname"
-\t\tginsatoa(widthptr, stringo);
-\
-\t\t// sigt[++].hash = hashcode
-\t\tgensatac(wi, b->hash);
-\
-\t\t// sigt[++].offset = of embedded struct
-\t\tgensatac(wi, 0);
-\
-\t\t// sigt[++].fun = &method
-\t\tgensatad(b->sym);
+\t\tot = rnd(ot, maxround); // base of substructure
+\t\tginsatoa(widthptr, stringo); // field name
+\t\tgensatac(wi, b->hash); // hash
+\tgensatac(wi, 0); // offset
+\t\tgensatad(b->sym); // &method
\t\tdatastring(b->name, strlen(b->name)+1);
\t}
コアとなるコードの解説
Sigt
とSigi
の構造体変更
この変更の核心は、Sigt
とSigi
という2つの重要なランタイムデータ構造に、型ハッシュとメソッドハッシュのための専用フィールドが追加されたことです。
-
Sigt
(Type Signature): 具象型の実行時表現です。thash
(type hash): 具象型全体のハッシュ値。型の構造(フィールド、メソッド、基底型など)から計算されます。これにより、型アサーションや型スイッチの際に、具象型の一致を高速に判定できます。mhash
(method hash): その具象型が実装するメソッド群のハッシュ値。インターフェースのメソッドセットとの互換性チェックを高速化するために使用されます。width
: 型のメモリ上でのサイズ(バイト単位)。alg
: 型のコピー、比較、ハッシュ計算などに使用されるアルゴリズムを識別する値。これにより、型に応じた適切な操作を効率的に選択できます。link
: ハッシュテーブルにSigt
エントリをリンクするためのポインタ。これにより、Sigt
の検索が高速化されます。meth
配列: 各メソッドの詳細(名前、ハッシュ、オフセット、関数ポインタ)を格納します。特にfhash
(フィールドハッシュ、ここではメソッドハッシュ)が追加され、個々のメソッドの一致判定も高速化されます。
-
Sigi
(Interface Signature): インターフェース型の実行時表現です。size
: インターフェースが要求するメソッドの数。meth
配列: インターフェースが要求する各メソッドの詳細(名前、ハッシュ、Sigt
内の関数位置)を格納します。ここでもfhash
が追加され、インターフェースが要求するメソッドと具象型が提供するメソッドのマッチングを高速化します。
コンパイラ (src/cmd/6g/obj.c
) の役割
src/cmd/6g/obj.c
のdumpsigt
関数は、コンパイル時にGoの型情報からSigt
構造体を生成し、実行可能ファイルに埋め込む役割を担っています。
変更前は、Sigt
の最初の要素に型全体のハッシュと幅を詰め込んでいましたが、変更後はthash
、mhash
、width
、alg
といった専用のフィールドにそれぞれ適切な値を書き込むようになりました。
typehash(progt, 0)
は具象型progt
のハッシュ値を計算し、sighash
はメソッドのハッシュ値を計算します。これらがそれぞれthash
とmhash
に格納されます。
この変更により、コンパイラが生成する型情報がより構造化され、ランタイムでの利用が容易かつ効率的になります。
ランタイム (src/runtime/iface.c
) の役割
src/runtime/iface.c
は、Goプログラムの実行時にインターフェースの動的な振る舞いを管理するGoランタイムの重要な部分です。
itype
関数: インターフェースの型アサーションや型変換の際に呼び出されます。この関数は、インターフェースが保持する具象型のSigt
と、ターゲットとなるインターフェースのSigi
を比較し、互換性のあるItype
(インターフェースの実行時型情報)を生成または検索します。変更後、itype
はSigt
のthash
とmhash
を利用して、ハッシュテーブルから既存のItype
を高速に検索できるようになりました。これにより、毎回メソッドの比較を行う必要がなくなり、パフォーマンスが向上します。sys·ifaceT2I
、sys·ifaceI2T
、sys·ifaceI2T2
: これらの関数は、型とインターフェース間の値の変換を処理します。変更後、Sigt
のwidth
とalg
フィールドを直接参照することで、値のコピーやメモリ割り当てのロジックが簡素化され、効率が向上しました。ifacehash
、ifaceeq
: インターフェースの値をハッシュしたり、比較したりする際に使用されます。これらの関数も、Sigt
のalg
とwidth
フィールドを利用して、型に応じた適切なハッシュ関数や比較関数を呼び出すようになりました。これにより、インターフェースをキーとするマップ操作などが高速化されます。
これらの変更は、Go言語のインターフェースが、その柔軟性を保ちつつも、実行時のオーバーヘッドを最小限に抑えるための初期の重要な最適化ステップであったと言えます。ハッシュ値の導入により、複雑な型比較が高速なハッシュ比較に置き換えられ、Goプログラム全体のパフォーマンス向上に寄与しました。
関連リンク
- Go言語のインターフェースに関する公式ドキュメントやチュートリアル(当時のものを見つけるのは難しいですが、現在のGoのインターフェースの概念は当時から大きく変わっていません)。
- Go言語のランタイムソースコード(特に
src/runtime/iface.go
やsrc/runtime/type.go
など、現在のインターフェースや型情報の定義)。
参考にした情報源リンク
- Go言語のソースコード(特にコミット履歴と関連ファイル)
src/cmd/6g/obj.c
src/cmd/gc/subr.c
src/runtime/iface.c
- Go言語のインターフェースに関する一般的な解説記事やドキュメント。
- ハッシュテーブル、ハッシュ関数に関する一般的なコンピュータサイエンスの知識。
- Go言語の初期開発に関する情報(Goの歴史、設計思想など)。
- The Go Programming Language (go.dev)
- Go at Google: Language Design in the Service of Software Engineering (research.google)