Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.csrc/cmd/gc/gen.cの2つのファイルに大きな変更を加えています。

src/cmd/6g/cgen.cの変更点

このファイルでは、componentgen関数のロジックが拡張されています。

  1. componentgen関数のコメント更新: 以前はスライス、文字列、インターフェースのみをサポートすると記載されていましたが、新たに「Small structs or arrays with elements of basic type are also supported.」(基本型の要素を持つ小さな構造体や配列もサポートされる)という記述が追加されました。

  2. TARRAY (配列) の処理拡張:

    • 以前はスライスの場合のみcomponentgenが適用されていましたが、今回の変更で、スライスではない配列(固定長配列)も対象となりました。
    • 具体的には、t->bound > 0 && t->bound <= 3 && !isfat(t->type)という条件が追加されました。これは、要素数が1から3までの小さな配列であり、かつその要素の型が「fat type」ではない(つまり、ポインタを含まない基本型などである)場合にcomponentgenを適用することを示しています。
    • この条件に合致しない配列は、引き続きcomponentgenの対象外となります。
  3. TSTRUCT (構造体) の新規処理追加:

    • 新たにTSTRUCTケースが追加され、構造体もcomponentgenの対象となりました。
    • 構造体の各フィールドをループし、isfat(t->type)でフィールドの型が「fat type」でないことを確認します。一つでも「fat type」のフィールドがあれば、その構造体はcomponentgenの対象外となります。
    • fldcount(フィールド数)をカウントし、fldcount == 0 || fldcount > 3という条件で、フィールド数が0または4以上の構造体は対象外となります。つまり、フィールド数が1から3までの小さな構造体のみが対象となります。
    • ゼロサイズの構造体は、他の場所で個別に処理されるため、ここでは除外されます。
  4. TARRAYおよびTSTRUCTの実際のコンポーネントコピーロジック:

    • TARRAYの場合、スライスではない固定長配列に対して、t->bound(要素数)の回数だけループし、各要素に対してclearslim(&nodl)(ゼロ値割り当ての場合)またはgmove(&nodr, &nodl)(コピーの場合)を呼び出すロジックが追加されました。nodl.xoffsetnodr.xoffsett->type->width(要素のサイズ)ずつインクリメントすることで、次の要素に移動します。
    • TSTRUCTの場合も同様に、構造体の各フィールドをループし、t->width(フィールドのオフセット)を使ってnodl.xoffsetnodr.xoffsetを計算し、各フィールドに対してclearslimまたはgmoveを呼び出すロジックが追加されました。

src/cmd/gc/gen.cの変更点

このファイルでは、主にゼロ値の割り当てに関するロジックが変更されています。

  1. clearslim(Node *n)関数の新規追加:

    • この関数は、与えられた「slim node」をゼロクリアするためのコードを生成します。
    • 様々な基本型(整数、浮動小数点、ブール、ポインタ、チャネル、マップ、複合数)に対して、それぞれのゼロ値を表すリテラルノードzを作成し、cgen(&z, n)を呼び出すことで、nzの値を割り当てるコードを生成します。
    • これにより、以前はcgen_as内で直接行われていたゼロ値の生成ロジックが、clearslimとして抽象化され、再利用可能になりました。
  2. cgen_as(Node *nl, Node *nr)関数の変更:

    • この関数は、nl = nrという代入操作のコードを生成します。
    • 以前は、nrN(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を作成し、そのopOLITERAL(リテラル値)に設定し、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に代入するコードを生成します。これにより、コンパイラは効率的なゼロクリア命令を生成できます。
  • cgen_asからのゼロ値生成ロジックの削除:
    • cgen_as関数は、代入操作を処理する主要な関数です。
    • 以前は、右辺がゼロ値(nr == N || isnil(nr))で、かつ左辺がfat typeでない場合、cgen_as内で直接ゼロ値リテラルを「発明」し、それを代入する複雑なロジックがありました。
    • 今回の変更で、この複雑なロジックが削除され、代わりにclearslim(nl);が呼び出されるようになりました。これにより、ゼロ値の生成と代入のロジックがclearslimに集約され、cgen_asのコードが大幅に簡潔になりました。

これらの変更により、Goコンパイラは、小さな配列や構造体に対しても、個々のコンポーネントを効率的にゼロクリアまたはコピーするコードを生成できるようになり、全体的なコンパイル済みコードのパフォーマンスが向上します。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/cmd/6g/src/cmd/gc/ディレクトリ)
  • Goコンパイラの内部構造に関する一般的な知識
  • Go言語のゼロ値に関するドキュメント