[インデックス 14384] ファイルの概要
このコミットは、Goコンパイラのcmd/6g
(x86-64アーキテクチャ向けコンパイラ)において、複合型(composite value)のコピーを最適化するcomponentgen
関数が、小さな配列と構造体にも適用されるように拡張するものです。これにより、これらの型のゼロ値の割り当てやコピーの効率が向上します。
コミット
commit 16072c749750b7e4e6e1e48d4724afdfec574efa
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Tue Nov 13 00:08:04 2012 +0100
cmd/6g: extend componentgen to small arrays and structs.
Fixes #4092.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6819083
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/16072c749750b7e4e6e1e48d4724afdfec574efa
元コミット内容
cmd/6g: extend componentgen to small arrays and structs.
Fixes #4092.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6819083
変更の背景
この変更は、Go言語のIssue #4092「cmd/6g
: componentgen
should handle small arrays and structs」を修正するために行われました。
componentgen
関数は、スライス、文字列、インターフェースといった複合型の値を、個々のコンポーネント(要素)を移動させることでコピーする最適化手法を実装していました。これは、大きなメモリブロックを一度にコピーするのではなく、より小さな単位で処理することで、特定の状況下でのパフォーマンス向上や、ガベージコレクションの効率化に寄与する可能性があります。
しかし、これまでのcomponentgen
は、小さな配列や構造体に対しては適用されていませんでした。これらの型も、そのサイズが小さい場合、コンポーネントごとのコピーが効率的であると考えられます。特に、ゼロ値を割り当てる際(例えば、変数の初期化時)に、メモリ全体をゼロクリアするのではなく、個々のフィールドをゼロクリアする方が、コンパイラが生成するコードをより効率的にできる場合があります。
このコミットは、このギャップを埋め、小さな配列や構造体に対してもcomponentgen
の最適化を適用することで、コンパイラが生成するコードの品質と実行効率をさらに向上させることを目的としています。
前提知識の解説
- Goコンパイラ (
cmd/6g
): Go言語のコンパイラは、ターゲットアーキテクチャごとに異なるフロントエンドを持っています。cmd/6g
は、x86-64(64ビットIntel/AMDプロセッサ)アーキテクチャ向けのGoコンパイラのフロントエンドです。Goのソースコードを解析し、中間表現を生成し、最終的に機械語に変換する役割の一部を担います。 - 複合型 (Composite Types): Go言語における複合型とは、複数の値や要素をまとめたデータ構造のことです。具体的には、配列(Array)、構造体(Struct)、スライス(Slice)、文字列(String)、インターフェース(Interface)などがこれに該当します。
componentgen
: Goコンパイラの内部関数で、複合型の値をコピーする際に、その値を構成する個々のコンポーネント(要素やフィールド)を一つずつコピーするコードを生成する役割を担います。これは、特定の条件下でメモリブロック全体のコピーよりも効率的になる場合があります。例えば、ガベージコレクタが追跡する必要があるポインタを含む型の場合、componentgen
はポインタと非ポインタのフィールドを区別して処理することで、より効率的なコードを生成できます。- ゼロ値 (Zero Value): Go言語では、変数を宣言すると自動的にその型のゼロ値で初期化されます。例えば、数値型は0、ブール型は
false
、文字列型は空文字列""
、ポインタ、スライス、マップ、チャネル、インターフェースはnil
です。このゼロ値の割り当ては、コンパイラによって効率的に行われる必要があります。 Node
: Goコンパイラの内部で、抽象構文木(AST)のノードを表すデータ構造です。プログラムの各要素(変数、式、型など)がNode
として表現されます。Type
: Goコンパイラの内部で、Go言語の型システムにおける型情報を表すデータ構造です。型の種類(etype
)、サイズ(width
)、配列の要素数(bound
)、構造体のフィールド情報(down
)などが含まれます。isfat(Type *t)
: コンパイラ内部で使われる関数で、与えられた型t
が「fat type」であるかどうかを判定します。「fat type」とは、ガベージコレクタが追跡する必要があるポインタを含む型、またはサイズが大きく、componentgen
のようなコンポーネントごとのコピーではなく、メモリブロック全体をコピーする方が効率的な型を指すことが多いです。例えば、スライス、文字列、インターフェース、マップ、チャネル、関数などがこれに該当します。clearslim(Node *n)
: このコミットで導入または変更される関数で、「slim node」をゼロクリアするコードを生成します。「slim node」は「fat type」の対義語で、ポインタを含まない、またはサイズが小さく、コンポーネントごとのゼロクリアが効率的な型を指します。
技術的詳細
このコミットは主にsrc/cmd/6g/cgen.c
とsrc/cmd/gc/gen.c
の2つのファイルに大きな変更を加えています。
src/cmd/6g/cgen.c
の変更点
このファイルでは、componentgen
関数のロジックが拡張されています。
-
componentgen
関数のコメント更新: 以前はスライス、文字列、インターフェースのみをサポートすると記載されていましたが、新たに「Small structs or arrays with elements of basic type are also supported.」(基本型の要素を持つ小さな構造体や配列もサポートされる)という記述が追加されました。 -
TARRAY
(配列) の処理拡張:- 以前はスライスの場合のみ
componentgen
が適用されていましたが、今回の変更で、スライスではない配列(固定長配列)も対象となりました。 - 具体的には、
t->bound > 0 && t->bound <= 3 && !isfat(t->type)
という条件が追加されました。これは、要素数が1から3までの小さな配列であり、かつその要素の型が「fat type」ではない(つまり、ポインタを含まない基本型などである)場合にcomponentgen
を適用することを示しています。 - この条件に合致しない配列は、引き続き
componentgen
の対象外となります。
- 以前はスライスの場合のみ
-
TSTRUCT
(構造体) の新規処理追加:- 新たに
TSTRUCT
ケースが追加され、構造体もcomponentgen
の対象となりました。 - 構造体の各フィールドをループし、
isfat(t->type)
でフィールドの型が「fat type」でないことを確認します。一つでも「fat type」のフィールドがあれば、その構造体はcomponentgen
の対象外となります。 fldcount
(フィールド数)をカウントし、fldcount == 0 || fldcount > 3
という条件で、フィールド数が0または4以上の構造体は対象外となります。つまり、フィールド数が1から3までの小さな構造体のみが対象となります。- ゼロサイズの構造体は、他の場所で個別に処理されるため、ここでは除外されます。
- 新たに
-
TARRAY
およびTSTRUCT
の実際のコンポーネントコピーロジック:TARRAY
の場合、スライスではない固定長配列に対して、t->bound
(要素数)の回数だけループし、各要素に対してclearslim(&nodl)
(ゼロ値割り当ての場合)またはgmove(&nodr, &nodl)
(コピーの場合)を呼び出すロジックが追加されました。nodl.xoffset
とnodr.xoffset
をt->type->width
(要素のサイズ)ずつインクリメントすることで、次の要素に移動します。TSTRUCT
の場合も同様に、構造体の各フィールドをループし、t->width
(フィールドのオフセット)を使ってnodl.xoffset
とnodr.xoffset
を計算し、各フィールドに対してclearslim
またはgmove
を呼び出すロジックが追加されました。
src/cmd/gc/gen.c
の変更点
このファイルでは、主にゼロ値の割り当てに関するロジックが変更されています。
-
clearslim(Node *n)
関数の新規追加:- この関数は、与えられた「slim node」をゼロクリアするためのコードを生成します。
- 様々な基本型(整数、浮動小数点、ブール、ポインタ、チャネル、マップ、複合数)に対して、それぞれのゼロ値を表すリテラルノード
z
を作成し、cgen(&z, n)
を呼び出すことで、n
にz
の値を割り当てるコードを生成します。 - これにより、以前は
cgen_as
内で直接行われていたゼロ値の生成ロジックが、clearslim
として抽象化され、再利用可能になりました。
-
cgen_as(Node *nl, Node *nr)
関数の変更:- この関数は、
nl = nr
という代入操作のコードを生成します。 - 以前は、
nr
がN
(nil)またはisnil(nr)
(ゼロ値の代入)の場合に、isfat(tl)
(左辺の型がfat typeの場合)であればclearfat(nl)
を呼び出し、そうでなければiszer
フラグを立ててゼロ値リテラルを「発明」し、それをnr
としてcgen
に渡していました。 - 今回の変更では、
isfat(tl)
でない場合(つまり、slim typeの場合)に、新しく追加されたclearslim(nl)
関数を呼び出すように変更されました。これにより、ゼロ値の生成ロジックがcgen_as
からclearslim
に委譲され、コードが簡潔になりました。 iszer
フラグや、ゼロ値リテラルを「発明」するための複雑なロジックが削除されました。
- この関数は、
src/cmd/6g/gg.h
の変更点
clearslim(Node*)
関数のプロトタイプ宣言が追加されました。これは、src/cmd/gc/gen.c
で定義されたclearslim
関数がsrc/cmd/6g/cgen.c
から呼び出されるため、ヘッダーファイルでの宣言が必要になったためです。
コアとなるコードの変更箇所
src/cmd/6g/cgen.c
--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -1465,7 +1467,10 @@ int
componentgen(Node *nr, Node *nl)
{
Node nodl, nodr;
+ Type *t;
int freel, freer;
+ vlong fldcount;
+ vlong loffset, roffset;
freel = 0;
freer = 0;
@@ -1475,8 +1480,33 @@ componentgen(Node *nr, Node *nl)
goto no;
case TARRAY:
- if(!isslice(nl->type))
+ t = nl->type;
+
+ // Slices are ok.
+ if(isslice(t))
+ break;
+ // Small arrays are ok.
+ if(t->bound > 0 && t->bound <= 3 && !isfat(t->type))
+ break;
+
+ goto no;
+
+ case TSTRUCT:
+ // Small structs with non-fat types are ok.
+ // Zero-sized structs are treated separately elsewhere.
+ fldcount = 0;
+ for(t=nl->type->type; t; t=t->down) {
+ if(isfat(t->type))
+ goto no;
+ if(t->etype != TFIELD)
+ fatal("componentgen: not a TFIELD: %lT", t);
+ fldcount++;
+ }
+ if(fldcount == 0 || fldcount > 3)
goto no;
+
+ break;
+
case TSTRING:
case TINTER:
break;
@@ -1500,6 +1530,23 @@ componentgen(Node *nr, Node *nl)
switch(nl->type->etype) {
case TARRAY:
+ // componentgen for arrays.
+ t = nl->type;
+ if(!isslice(t)) {
+ nodl.type = t->type;
+ nodr.type = nodl.type;
+ for(fldcount=0; fldcount < t->bound; fldcount++) {
+ if(nr == N)
+ clearslim(&nodl);
+ else
+ gmove(&nodr, &nodl);
+ nodl.xoffset += t->type->width;
+ nodr.xoffset += t->type->width;
+ }
+ goto yes;
+ }
+
+ // componentgen for slices.
nodl.xoffset += Array_array;
nodl.type = ptrto(nl->type->type);
@@ -1577,6 +1624,23 @@ componentgen(Node *nr, Node *nl)
gmove(&nodr, &nodl);
goto yes;
+
+ case TSTRUCT:
+ loffset = nodl.xoffset;
+ roffset = nodr.xoffset;
+ for(t=nl->type->type; t; t=t->down) {
+ nodl.xoffset = loffset + t->width;
+ nodl.type = t->type;
+
+ if(nr == N)
+ clearslim(&nodl);
+ else {
+ nodr.xoffset = roffset + t->width;
+ nodr.type = nodl.type;
+ gmove(&nodr, &nodl);
+ }
+ }
+ goto yes;
}
no:
src/cmd/gc/gen.c
--- a/src/cmd/gc/gen.c
+++ b/src/cmd/gc/gen.c
@@ -632,6 +632,67 @@ cgen_discard(Node *nr)
}
}
+/*
+ * clearslim generates code to zero a slim node.
+ */
+void
+clearslim(Node *n)
+{
+ Node z;
+ Mpflt zero;
+
+ memset(&z, 0, sizeof(z));
+ z.op = OLITERAL;
+ z.type = n->type;
+ z.addable = 1;
+
+ switch(simtype[n->type->etype]) {
+ case TCOMPLEX64:
+ case TCOMPLEX128:
+ z.val.u.cval = mal(sizeof(z.val.u.cval));
+ mpmovecflt(&z.val.u.cval->real, 0.0);
+ mpmovecflt(&z.val.u.cval->imag, 0.0);
+ break;
+
+ case TFLOAT32:
+ case TFLOAT64:
+ mpmovecflt(&zero, 0.0);
+ z.val.ctype = CTFLT;
+ z.val.u.fval = &zero;
+ break;
+
+ case TPTR32:
+ case TPTR64:
+ case TCHAN:
+ case TMAP:
+ z.val.ctype = CTNIL;
+ break;
+
+ case TBOOL:
+ z.val.ctype = CTBOOL;
+ break;
+
+ case TINT8:
+ case TINT16:
+ case TINT32:
+ case TINT64:
+ case TUINT8:
+ case TUINT16:
+ case TUINT32:
+ case TUINT64:
+ z.val.ctype = CTINT;
+ z.val.u.xval = mal(sizeof(z.val.u.xval));
+ mpmovecfix(z.val.u.xval, 0);
+ break;
+
+ default:
+ fatal("clearslim called on type %T", n->type);
+ }
+
+ ullmancalc(&z);
+ cgen(&z, n);
+}
+
/*
* generate assignment:
* nl = nr
@@ -640,9 +701,7 @@ cgen_as(Node *nl, Node *nr)
void
cgen_as(Node *nl, Node *nr)
{
- Node nc;
Type *tl;
- int iszer;
if(debug['g']) {
dump("cgen_as", nl);
@@ -657,7 +716,6 @@ cgen_as(Node *nl, Node *nr)
return;
}
- iszer = 0;
if(nr == N || isnil(nr)) {
// externals and heaps should already be clear
if(nr == N) {
@@ -672,59 +730,12 @@ cgen_as(Node *nl, Node *nr)
return;
if(isfat(tl)) {
clearfat(nl);
- goto ret;
- }
-
- /* invent a "zero" for the rhs */
- iszer = 1;
- nr = &nc;
- memset(nr, 0, sizeof(*nr));
- switch(simtype[tl->etype]) {
- default:
- fatal("cgen_as: tl %T", tl);
- break;
-
- case TINT8:
- case TUINT8:
- case TINT16:
- case TUINT16:
- case TINT32:
- case TUINT32:
- case TINT64:
- case TUINT64:
- nr->val.u.xval = mal(sizeof(*nr->val.u.xval));
- mpmovecfix(nr->val.u.xval, 0);
- nr->val.ctype = CTINT;
- break;
-
- case TFLOAT32:
- case TFLOAT64:
- nr->val.u.fval = mal(sizeof(*nr->val.u.fval));
- mpmovecflt(nr->val.u.fval, 0.0);
- nr->val.ctype = CTFLT;
- break;
-
- case TBOOL:
- nr->val.u.bval = 0;
- nr->val.ctype = CTBOOL;
- break;
-
- case TPTR32:
- case TPTR64:
- nr->val.ctype = CTNIL;
- break;
-
- case TCOMPLEX64:
- case TCOMPLEX128:
- nr->val.u.cval = mal(sizeof(*nr->val.u.cval));
- mpmovecflt(&nr->val.u.cval->real, 0.0);
- mpmovecflt(&nr->val.u.cval->imag, 0.0);
- break;
+ return;
}
- nr->op = OLITERAL;
- nr->type = tl;
- nr->addable = 1;
- ullmancalc(nr);
+ clearslim(nl);
+ if(nl->addable)
+ gused(nl);
+ return;
}
tl = nl->type;
@@ -732,11 +743,6 @@ cgen_as(Node *nl, Node *nr)
return;
cgen(nr, nl);
- if(iszer && nl->addable)
- gused(nl);
-
-ret:
- ;
}
/*
コアとなるコードの解説
src/cmd/6g/cgen.c
におけるcomponentgen
の拡張
- 型判定と条件分岐:
componentgen
関数は、nl->type->etype
(左辺のノードの型)に基づいて処理を分岐します。TARRAY
ケースでは、まずisslice(t)
でスライスかどうかを判定します。スライスであれば既存のロジック(break
で抜けて、後続のスライス処理へ)に従います。- スライスでない固定長配列の場合、
t->bound > 0 && t->bound <= 3 && !isfat(t->type)
という条件で、要素数が1から3までの小さな配列であり、かつ要素の型がポインタを含まない(!isfat
)場合にのみ、componentgen
の対象とします。この条件に合致しない場合はgoto no;
で処理をスキップします。 TSTRUCT
ケースが新設され、構造体も対象となりました。構造体の各フィールドをfor(t=nl->type->type; t; t=t->down)
で走査し、isfat(t->type)
でフィールドがポインタを含むかチェックします。一つでもポインタを含むフィールドがあればgoto no;
でスキップします。また、フィールド数fldcount
が1から3の範囲外であればgoto no;
でスキップします。
- コンポーネントごとの処理:
TARRAY
の処理ブロック内で、if(!isslice(t))
の条件が真の場合(固定長配列の場合)、for(fldcount=0; fldcount < t->bound; fldcount++)
ループで各要素を処理します。nr == N
(ゼロ値の割り当て)の場合、新しく導入されたclearslim(&nodl)
を呼び出して要素をゼロクリアします。- それ以外の場合(コピー)は、
gmove(&nodr, &nodl)
を呼び出して要素をコピーします。 nodl.xoffset += t->type->width;
とnodr.xoffset += t->type->width;
で、次の要素のメモリ位置にオフセットを進めます。
TSTRUCT
の処理ブロックでは、for(t=nl->type->type; t; t=t->down)
ループで各フィールドを処理します。nodl.xoffset = loffset + t->width;
とnodr.xoffset = roffset + t->width;
で、現在のフィールドのオフセットを計算します。nr == N
(ゼロ値の割り当て)の場合、clearslim(&nodl)
を呼び出してフィールドをゼロクリアします。- それ以外の場合(コピー)は、
gmove(&nodr, &nodl)
を呼び出してフィールドをコピーします。
src/cmd/gc/gen.c
におけるゼロ値割り当ての抽象化
clearslim
関数の導入:- この関数は、Goコンパイラのバックエンドに近い部分で、特定の型のノード(
slim node
)をゼロクリアするための機械語命令を生成する役割を担います。 Node z;
で一時的なノードz
を作成し、そのop
をOLITERAL
(リテラル値)に設定し、type
をゼロクリア対象のノードn
の型に合わせます。switch(simtype[n->type->etype])
で、ゼロクリア対象の型の種類に応じて、適切なゼロ値リテラル(整数0、浮動小数点数0.0、nilなど)をz.val
に設定します。例えば、整数型であればmpmovecfix(z.val.u.xval, 0)
で多倍長整数を0に設定します。- 最後に
ullmancalc(&z);
でz
のUllman数を計算し、cgen(&z, n);
を呼び出すことで、z
(ゼロ値リテラル)をn
に代入するコードを生成します。これにより、コンパイラは効率的なゼロクリア命令を生成できます。
- この関数は、Goコンパイラのバックエンドに近い部分で、特定の型のノード(
cgen_as
からのゼロ値生成ロジックの削除:cgen_as
関数は、代入操作を処理する主要な関数です。- 以前は、右辺がゼロ値(
nr == N || isnil(nr)
)で、かつ左辺がfat type
でない場合、cgen_as
内で直接ゼロ値リテラルを「発明」し、それを代入する複雑なロジックがありました。 - 今回の変更で、この複雑なロジックが削除され、代わりに
clearslim(nl);
が呼び出されるようになりました。これにより、ゼロ値の生成と代入のロジックがclearslim
に集約され、cgen_as
のコードが大幅に簡潔になりました。
これらの変更により、Goコンパイラは、小さな配列や構造体に対しても、個々のコンポーネントを効率的にゼロクリアまたはコピーするコードを生成できるようになり、全体的なコンパイル済みコードのパフォーマンスが向上します。
関連リンク
- Go Issue #4092:
cmd/6g
:componentgen
should handle small arrays and structs - https://github.com/golang/go/issues/4092 - Go CL 6819083:
cmd/6g: extend componentgen to small arrays and structs.
- https://golang.org/cl/6819083
参考にした情報源リンク
- Go言語のソースコード(特に
src/cmd/6g/
とsrc/cmd/gc/
ディレクトリ) - Goコンパイラの内部構造に関する一般的な知識
- Go言語のゼロ値に関するドキュメント