[インデックス 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/