[インデックス 14643] ファイルの概要
このコミットは、Go言語のコンパイラであるcmd/5g
(ARM), cmd/6g
(x86-64), cmd/8g
(x86) における内部データ構造であるProg
とAddr
のサイズを削減することを目的としています。具体的には、Addr
構造体内のフィールドをunion
でまとめることと、Prog
構造体のフィールドの順序を変更することで、メモリ使用量を最適化し、コンパイラのパフォーマンス向上に寄与しています。
コミット
commit b2797f2ae09680eafe4359fc7c014ef35d27ccdc
Author: Dave Cheney <dave@cheney.net>
Date: Fri Dec 14 06:20:24 2012 +1100
cmd/{5,6,8}g: reduce size of Prog and Addr
5g: Prog went from 128 bytes to 88 bytes
6g: Prog went from 174 bytes to 144 bytes
8g: Prog went from 124 bytes to 92 bytes
There may be a little more that can be squeezed out of Addr, but alignment will be a factor.
All: remove the unused pun field from Addr
R=rsc, minux.ma
CC=golang-dev
https://golang.org/cl/6922048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b2797f2ae09680eafe4359fc7c014ef35d27ccdc
元コミット内容
cmd/{5,6,8}g: reduce size of Prog and Addr
5g: Prog went from 128 bytes to 88 bytes
6g: Prog went from 174 bytes to 144 bytes
8g: Prog went from 124 bytes to 92 bytes
There may be a little more that can be squeezed out of Addr, but alignment will be a factor.
All: remove the unused pun field from Addr
変更の背景
Go言語のコンパイラは、コンパイル時にプログラムの抽象構文木 (AST) を中間表現に変換し、最終的に機械語コードを生成します。この過程で、Prog
(プログラム命令) と Addr
(アドレス/オペランド) といったデータ構造が頻繁に生成され、メモリ上に大量に存在します。これらの構造体のサイズが大きいと、メモリ使用量が増加し、コンパイル時間やコンパイラ自体の実行速度に悪影響を及ぼします。
このコミットの主な目的は、これらのコアデータ構造のサイズを削減し、コンパイラのメモリフットプリントを減らすことで、全体的なパフォーマンスを向上させることです。特に、Prog
とAddr
のサイズ削減は、コンパイラの実行効率に直接的な影響を与えます。コミットメッセージに記載されているように、各アーキテクチャのコンパイラでProg
構造体のサイズが大幅に削減されています(例: 5gで128バイトから88バイトへ)。
また、Addr
構造体から未使用のpun
フィールドを削除することも、サイズ削減の一環として行われています。
前提知識の解説
Goコンパイラ (cmd/{5,6,8}g
)
Go言語の初期のコンパイラは、Plan 9 Cコンパイラツールチェーンをベースにしていました。5g
, 6g
, 8g
はそれぞれ異なるアーキテクチャをターゲットとするコンパイラを指します。
5g
: ARMアーキテクチャ向け6g
: x86-64 (AMD64) アーキテクチャ向け8g
: x86 (32-bit Intel) アーキテクチャ向け
これらのコンパイラは、Goのソースコードを解析し、中間表現を生成し、最終的にターゲットアーキテクチャの機械語コードに変換します。
Prog
構造体
Prog
構造体は、コンパイラの中間表現における単一の「プログラム命令」または「操作」を表します。これは、アセンブリ言語の命令に似た抽象的な表現であり、実際の機械語命令に変換される前の段階のものです。Prog
には、操作の種類(as
フィールド)、ソースアドレス(from
フィールド)、デスティネーションアドレス(to
フィールド)、次の命令へのリンク(link
フィールド)などが含まれます。
Addr
構造体
Addr
構造体は、Prog
構造体内で使用される「アドレス」または「オペランド」を表します。これは、命令が操作するデータやメモリ位置、あるいはジャンプ先の情報などを保持します。Addr
には、オフセット(offset
)、シンボル(sym
)、型(type
)、レジスタ(reg
)などの情報が含まれます。また、定数値(浮動小数点数dval
、文字列sval
)や、分岐命令のターゲットとなるProg
へのポインタ(branch
)など、様々な種類のデータを保持する可能性があります。
C言語のunion
union
(共用体)はC言語の機能の一つで、複数の異なる型のメンバーが同じメモリ領域を共有することを可能にします。union
のサイズは、そのメンバーの中で最も大きい型のサイズによって決まります。これにより、ある時点でいずれか一つのメンバーしか使用されない場合に、メモリを節約することができます。例えば、Addr
構造体があるときは浮動小数点数を保持し、別のときは文字列を保持するといった場合にunion
が有効です。
構造体のアライメントとパディング
C言語(およびGo言語のコンパイラが生成するコード)では、メモリ効率とCPUのアクセス効率を向上させるために、構造体のメンバーが特定のバイト境界に配置されることがあります。これを「アライメント」と呼びます。アライメント要件を満たすために、コンパイラは構造体のメンバー間に「パディング」と呼ばれる未使用のバイトを挿入することがあります。パディングは構造体のサイズを増加させる要因となります。フィールドの順序を適切に並べ替えることで、パディングを最小限に抑え、構造体の全体サイズを削減できる場合があります。
pun
フィールド
コミットメッセージに「remove the unused pun field from Addr」とあるように、pun
フィールドはAddr
構造体から削除されました。このフィールドは、おそらく「pointer union」または「pseudo union」のような意味合いで、過去に何らかの目的で使われていたが、現在は不要になったものと推測されます。未使用のフィールドを削除することは、構造体サイズ削減の直接的な方法です。
技術的詳細
このコミットにおける主要な技術的変更点は以下の通りです。
-
Addr
構造体におけるunion
の導入:src/cmd/{5g,6g,8g}/gg.h
において、Addr
構造体内のdval
(double),branch
(Prog*),sval
(char[NSNAME]) の各フィールドがu
という名前のunion
内に移動されました。- さらに、
vval
(vlong) フィールドがこのunion
に追加されました。 - これにより、これらのフィールドはメモリ上で同じ領域を共有するため、
Addr
構造体のサイズが大幅に削減されます。例えば、dval
が使用される場合はその領域が浮動小数点数として解釈され、branch
が使用される場合はポインタとして解釈されます。 - この変更に伴い、これらのフィールドへのアクセスは
a->dval
からa->u.dval
のように変更されました。
変更前 (
5g
の例):struct Addr { int32 offset; int32 offset2; double dval; Prog* branch; char sval[NSNAME]; // ... char pun; // 削除されるフィールド // ... };
変更後 (
5g
の例):struct Addr { int32 offset; int32 offset2; union { double dval; vlong vval; Prog* branch; char sval[NSNAME]; } u; // ... // char pun; は削除 // ... };
-
Addr
構造体からのpun
フィールドの削除:Addr
構造体からuchar pun;
フィールドが完全に削除されました。これは未使用であったため、直接的なサイズ削減に貢献します。
-
Prog
構造体のフィールド順序の変更:src/cmd/{5g,6g,8g}/gg.h
において、Prog
構造体のフィールドの宣言順序が変更されました。- 特に、
short as;
(opcode) フィールドが、uint32 lineno;
の後から、void* regp;
の後に移動されています。 - この変更は、構造体のアライメントを最適化し、コンパイラが挿入するパディングバイトを削減することを目的としています。これにより、構造体の全体サイズが小さくなります。
変更前 (
5g
の例):struct Prog { short as; // opcode uint32 loc; // pc offset in this func uint32 lineno; // source line that generated this Addr from; // src address Addr to; // dst address Prog* link; // next instruction in this func void* regp; // points to enclosing Reg struct uchar reg; // doubles as width in DATA op uchar scond; };
**変更後 (
5g
の例):struct Prog { uint32 loc; // pc offset in this func uint32 lineno; // source line that generated this Prog* link; // next instruction in this func void* regp; // points to enclosing Reg struct short as; // opcode uchar reg; // doubles as width in DATA op uchar scond; Addr from; // src address Addr to; // dst address };
これらの変更により、各コンパイラにおけるProg
構造体のサイズが以下のように削減されました。
5g
: 128バイト -> 88バイト6g
: 174バイト -> 144バイト8g
: 124バイト -> 92バイト
コアとなるコードの変更箇所
このコミットは、主に以下のファイル群に影響を与えています。
src/cmd/{5g,6g,8g}/gg.h
:Addr
およびProg
構造体の定義が変更されています。Addr
構造体では、dval
,branch
,sval
がunion u
内に移動し、vval
が追加され、pun
フィールドが削除されました。Prog
構造体では、フィールドの順序が変更されました。
src/cmd/{5g,6g,8g}/gobj.c
:Addr
構造体のメンバーへのアクセスがa->u.dval
,a->u.branch
,a->u.sval
のように変更されています。zaddr
関数やgdatacomplex
関数などでこの変更が見られます。src/cmd/{5g,6g,8g}/gsubr.c
:gbranch
,patch
,unpatch
,naddr
などの関数で、Addr
構造体のbranch
やdval
フィールドへのアクセスがa->u.branch
,a->u.dval
のように変更されています。src/cmd/{5g,6g,8g}/list.c
:Dconv
関数など、Addr
構造体の内容を文字列として表示する部分で、a->u.branch
,a->u.dval
,a->u.sval
へのアクセスに変更されています。src/cmd/{5g,6g,8g}/reg.c
:regopt
,chasejmp
,mark
,fixjmp
などの関数で、Addr
構造体のbranch
フィールドへのアクセスがp->to.u.branch
のように変更されています。src/cmd/{6g,8g}/peep.c
:loop
関数内で、p->from.dval
の比較がp->from.u.vval
の比較に変更されています。これは、dval
がunion
内に移動し、vval
が追加されたことによる影響です。
コアとなるコードの解説
Addr
構造体の変更と影響
Addr
構造体におけるunion u
の導入は、コンパイラコード全体にわたるAddr
メンバーへのアクセス方法の変更を必要としました。例えば、以前はa->branch
と直接アクセスしていた箇所は、a->u.branch
と変更する必要があります。これは、Addr
構造体を使用するほぼ全てのファイルに影響を及ぼす大規模な変更です。
-
src/cmd/5g/gg.h
(Addr構造体):--- a/src/cmd/5g/gg.h +++ b/src/cmd/5g/gg.h @@ -15,9 +15,13 @@ struct Addr { int32 offset; int32 offset2; - double dval; - Prog* branch; - char sval[NSNAME]; + + union { + double dval; + vlong vval; + Prog* branch; + char sval[NSNAME]; + } u; Sym* sym; Node* node; @@ -25,7 +29,6 @@ struct Addr uchar type; char name; uchar reg; - char pun; // 削除 uchar etype; };
-
src/cmd/5g/gobj.c
(アクセス例):--- a/src/cmd/5g/gobj.c +++ b/src/cmd/5g/gobj.c @@ -128,9 +128,9 @@ zaddr(Biobuf *b, Addr *a, int s) break; case D_BRANCH: - if(a->branch == nil) + if(a->u.branch == nil) fatal("unpatched branch"); - a->offset = a->branch->loc; + a->offset = a->u.branch->loc; l = a->offset; Bputc(b, l); Bputc(b, l>>8);
この変更は、
Addr
構造体の内部表現が変わったため、そのメンバーにアクセスするすべてのコードパスで修正が必要であることを示しています。
Prog
構造体のフィールド順序変更と影響
Prog
構造体のフィールド順序の変更は、主にメモリレイアウトの最適化を目的としています。これは、C言語の構造体パディングの挙動を考慮したもので、コンパイラが自動的に挿入するパディングバイトを最小限に抑えることで、構造体全体のサイズを削減します。この変更自体は、Prog
構造体のメンバーへのアクセス方法を変更するものではないため、Prog
構造体を使用するコードの修正は最小限で済みます。しかし、構造体のサイズが小さくなることで、メモリキャッシュの効率が向上し、コンパイラの実行速度に良い影響を与える可能性があります。
src/cmd/5g/gg.h
(Prog構造体):--- a/src/cmd/5g/gg.h +++ b/src/cmd/5g/gg.h @@ -29,12 +29,12 @@ struct Prog { - short as; // opcode uint32 loc; // pc offset in this func uint32 lineno; // source line that generated this - Addr from; // src address - Addr to; // dst address Prog* link; // next instruction in this func void* regp; // points to enclosing Reg struct + short as; // opcode uchar reg; // doubles as width in DATA op uchar scond; + Addr from; // src address + Addr to; // dst address };
as
フィールドが移動し、from
とto
フィールドが最後に移動していることがわかります。これは、より大きなアライメント要件を持つフィールド(ポインタやAddr
構造体自体)をまとめて配置することで、パディングを減らす一般的な最適化手法です。
これらの変更は、Goコンパイラの内部構造の深い理解と、C言語のメモリ管理に関する知識に基づいています。コンパイラのパフォーマンスを向上させるための、低レベルかつ重要な最適化と言えます。
関連リンク
- Go CL 6922048: https://golang.org/cl/6922048
参考にした情報源リンク
- Go言語のコンパイラに関する一般的な情報:
- The Go Programming Language Specification: https://go.dev/ref/spec
- Go compiler internals (various blog posts and talks, e.g., "Go's Toolchain" by Rob Pike, "Inside the Go Compiler" by Russ Cox)
- C言語の
union
と構造体パディングに関する情報:- C Union: https://www.geeksforgeeks.org/union-in-c/
- Structure Padding and Packing: https://www.geeksforgeeks.org/structure-padding-and-packing-in-c-language/