[インデックス 14945] ファイルの概要
このコミットは、Go言語のリンカである cmd/5l
におけるメモリ使用量の最適化に関するものです。具体的には、Adr
(アドレス) および Prog
(プログラム命令) 構造体のサイズを削減するために、offset2
フィールドを Adr
構造体内の u0
共用体 (union) の中に移動しています。これにより、リンカのメモリフットプリントが削減され、特に大規模なGoプログラムのリンク時におけるメモリ効率が向上します。
コミット
commit e00c9f0dbb7b53410308101311bd0a448e00e17b
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Jan 22 02:50:27 2013 +0800
cmd/5l: move offset2 into Adr.u0 union to save 4/8 bytes for Adr/Prog resp.
sizeof(Adr) from 24 bytes down to 20 bytes.
sizeof(Prog) from 84 bytes down to 76 bytes.
5l linking cmd/godoc statistics:
Before:
Maximum resident set size (kbytes): 106668
After:
Maximum resident set size (kbytes): 99412
R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/7100059
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e00c9f0dbb7b53410308101311bd0a448e00e17b
元コミット内容
Go言語のリンカ cmd/5l
において、Adr
構造体と Prog
構造体のメモリ使用量を削減するため、offset2
フィールドを Adr
構造体内の u0
共用体 (union) の中に移動しました。この変更により、Adr
構造体のサイズは24バイトから20バイトに、Prog
構造体のサイズは84バイトから76バイトに削減されました。結果として、cmd/godoc
のリンク時における最大常駐セットサイズ (Maximum resident set size) が106668KBから99412KBに減少し、約7MBのメモリ削減が達成されました。
変更の背景
この変更の主な背景は、Go言語のリンカ cmd/5l
のメモリ効率を向上させることです。リンカは、コンパイルされたオブジェクトファイルを結合して実行可能ファイルを生成するプロセスにおいて、大量のデータ構造(Adr
や Prog
など)をメモリ上に保持します。これらのデータ構造のサイズがわずかでも削減されれば、リンクプロセス全体のメモリフットプリントが大幅に減少する可能性があります。
特に、cmd/godoc
のような比較的大きなGoプログラムをリンクする際に、リンカが消費するメモリ量が問題となることがありました。メモリ使用量の削減は、ビルド時間の短縮、より少ないリソースでのビルド環境の実現、および大規模プロジェクトにおけるスケーラビリティの向上に貢献します。
このコミットは、Adr
構造体内の offset2
フィールドが、u0
共用体内の他のフィールド (u0offset
, u0sval
など) と同時に使用されることがない、あるいは特定のコンテキストでのみ使用されるという前提に基づいています。共用体を使用することで、異なる目的で使われるが同時に必要とされないフィールドが同じメモリ領域を共有できるようになり、構造体全体のサイズを削減できます。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が役立ちます。
-
Go言語のツールチェインとリンカ (
cmd/5l
):- Go言語のビルドプロセスは、ソースコードをコンパイルし、その結果生成されるオブジェクトファイルをリンカが結合して実行可能ファイルを生成します。
cmd/5l
は、Go言語のツールチェインの一部であり、ARMアーキテクチャ (Plan 9 from Bell Labsの命名規則では5
がARMを指す) 向けのリンカです。Goのツールチェインはクロスコンパイルをサポートしており、各アーキテクチャに対応するリンカが存在します(例:6l
はamd64、8l
は386)。- リンカは、プログラム内のシンボル解決、アドレスの再配置、ライブラリの結合などを行います。この過程で、プログラムの命令やデータに関する情報を内部的なデータ構造として表現します。
-
C言語の構造体 (struct) と共用体 (union):
- 構造体 (struct): 複数の異なる型の変数を一つにまとめた複合データ型です。構造体のサイズは、そのメンバーの型のサイズの合計(およびアライメントによるパディング)によって決まります。各メンバーは独立したメモリ領域を占めます。
- 共用体 (union): 複数の異なる型の変数を一つにまとめた複合データ型ですが、構造体とは異なり、すべてのメンバーが同じメモリ領域を共有します。共用体のサイズは、その中で最も大きいメンバーのサイズによって決まります。共用体のメンバーは一度に一つしか有効な値を保持できません。この特性を利用して、メモリを節約することができます。
-
Adr
構造体とProg
構造体:- Go言語のリンカ内部で使用されるデータ構造です。
Adr
(Address) 構造体は、命令のオペランド(引数)やデータのアドレスを表すために使用されます。これには、オフセット、シンボルへのポインタ、型情報などが含まれます。Prog
(Program) 構造体は、アセンブリ命令そのものを表します。各Prog
は、命令コード、ソースとデスティネーションのオペランド(これらがAdr
構造体で表現される)、およびその他の命令固有の情報を持ちます。- リンカは、プログラム全体を
Prog
構造体のリストとして表現し、各Prog
が参照するアドレス情報をAdr
構造体で管理します。
-
sizeof
演算子:- C言語において、データ型や変数が占めるメモリのバイト数を返す演算子です。構造体や共用体のサイズを計算する際に使用されます。
-
最大常駐セットサイズ (Maximum Resident Set Size, RSS):
- プロセスが物理メモリ(RAM)上で占有している最大量を示す指標です。これは、プロセスが実際に使用しているメモリの量であり、仮想メモリとは異なります。リンカのようなメモリを大量に消費するアプリケーションでは、この値がパフォーマンスに直結します。単位は通常キロバイト (KB) です。
-
int32
:- 32ビット符号付き整数型です。通常4バイトのメモリを占めます。
技術的詳細
このコミットの核心は、Adr
構造体の定義変更にあります。変更前と変更後の Adr
構造体の定義を比較することで、メモリ削減のメカニズムを理解できます。
変更前の Adr
構造体 (src/cmd/5l/l.h
より抜粋):
struct Adr
{
union
{
int32 u0offset;
char* u0sval;
Ieee u0ieee;
char* u0sbig;
} u0;
Sym* sym;
Sym* gotype;
int32 offset2; // argsize
char type;
char reg;
char name;
char class;
};
#define offset u0.u0offset
この定義では、u0
共用体の外に int32 offset2;
というフィールドが独立して存在していました。u0
共用体は、u0offset
(32ビット整数)、u0sval
(ポインタ)、u0ieee
(浮動小数点数)、u0sbig
(ポインタ) のいずれかを保持できます。共用体のサイズは、その中で最も大きいメンバーのサイズによって決まります。仮にポインタが8バイト、int32
が4バイト、Ieee
が8バイトとすると、u0
共用体は8バイトを占めます。
したがって、変更前の Adr
構造体のサイズは、u0
(8バイト) + sym
(ポインタ、8バイト) + gotype
(ポインタ、8バイト) + offset2
(4バイト) + type
(1バイト) + reg
(1バイト) + name
(1バイト) + class
(1バイト) となり、アライメントを考慮すると合計24バイトになります。
変更後の Adr
構造体 (src/cmd/5l/l.h
より抜粋):
struct Adr
{
union
{
struct {
int32 offset;
int32 offset2; // argsize
} u0off;
char* u0sval;
Ieee u0ieee;
char* u0sbig;
} u0;
Sym* sym;
Sym* gotype;
char type;
char reg;
char name;
char class;
};
#define offset u0.u0off.offset
#define offset2 u0.u0off.offset2
変更後では、offset2
フィールドが u0
共用体の中に新しく導入された匿名構造体 u0off
のメンバーとして移動されました。u0off
構造体は int32 offset;
と int32 offset2;
の2つの int32
メンバーを持ち、合計8バイトを占めます(アライメントを考慮しない場合)。
この変更のポイントは、offset
と offset2
が同時に使用される場合に、これらが u0
共用体内の他のメンバー (u0sval
, u0ieee
, u0sbig
) とは排他的に使用されるという前提があることです。つまり、Adr
構造体が offset
と offset2
のペアを必要とする場合、それは文字列値 (u0sval
) や浮動小数点値 (u0ieee
) を必要としないコンテキストである、ということです。
これにより、u0
共用体の最大サイズは変わらず(依然としてポインタや Ieee
のサイズに依存)、しかし offset2
が共用体の外から中へ移動したため、構造体全体のサイズから offset2
の4バイト分が削減されます。
結果として、Adr
構造体のサイズは24バイトから20バイトに削減されます。
Prog
構造体への影響:
Prog
構造体は、そのメンバーとして Adr
構造体を含んでいます。リンカの内部では、多くの Prog
構造体が生成されます。Prog
構造体は通常、ソースオペランドとデスティネーションオペランドのために複数の Adr
構造体を持つため、Adr
構造体1つあたりのサイズ削減が、Prog
構造体全体のサイズに累積的に影響します。
コミットメッセージによると、Prog
構造体のサイズは84バイトから76バイトに削減されたとあります。これは、Prog
構造体が2つの Adr
構造体(ソースとデスティネーション)を持つ場合、2 * 4バイト = 8バイト
の削減となり、コミットメッセージの数値と一致します。
このメモリ削減は、リンカが処理する命令やアドレスの数が多いほど顕著な効果を発揮します。cmd/godoc
のリンク統計が示しているように、約7MBの常駐セットサイズの削減は、大規模なGoアプリケーションのビルドにおいて無視できない改善です。
コアとなるコードの変更箇所
変更は src/cmd/5l/l.h
ファイルに集中しています。
--- a/src/cmd/5l/l.h
+++ b/src/cmd/5l/l.h
@@ -68,21 +68,24 @@ struct Adr
{
union
{
- int32 u0offset;
+ struct {
+ int32 offset;
+ int32 offset2; // argsize
+ } u0off;
char* u0sval;
Ieee u0ieee;
char* u0sbig;
} u0;
Sym* sym;
Sym* gotype;
- int32 offset2; // argsize
char type;
char reg;
char name;
char class;
};
-#define offset u0.u0offset
+#define offset u0.u0off.offset
+#define offset2 u0.u0off.offset2
#define sval u0.u0sval
#define scon sval
#define ieee u0.u0ieee
コアとなるコードの解説
-
Adr
構造体の変更:- 元のコードでは、
u0
共用体の直後にint32 offset2;
が独立したメンバーとして定義されていました。 - 変更後のコードでは、
int32 offset2;
がu0
共用体の中に新しく定義された匿名構造体u0off
のメンバーとして移動されました。 u0off
構造体はint32 offset;
とint32 offset2;
の2つのメンバーを持ちます。これにより、offset
とoffset2
が論理的に関連付けられ、かつメモリを共有するu0
共用体の一部となります。
- 元のコードでは、
-
#define
マクロの変更:- 元の
#define offset u0.u0offset
は、u0
共用体内のu0offset
メンバーを直接参照していました。 - 変更後の
#define offset u0.u0off.offset
は、u0
共用体内のu0off
構造体を経由してoffset
メンバーを参照するように変更されました。 - 新しく
#define offset2 u0.u0off.offset2
が追加され、offset2
メンバーも同様にu0off
構造体を経由して参照できるようになりました。 - これらのマクロは、コードの可読性を保ちつつ、基盤となる構造体メンバーへのアクセスを簡素化するために使用されます。
- 元の
この変更により、offset
と offset2
が同時に使用される場合でも、それらが u0
共用体の他のメンバー(u0sval
、u0ieee
など)とは排他的にメモリを共有するため、Adr
構造体全体のサイズが削減されます。これは、リンカが Adr
構造体のインスタンスを多数作成する際に、メモリ使用量を効率的に削減するための典型的なC言語の最適化手法です。
関連リンク
- Go CL 7100059: https://golang.org/cl/7100059
参考にした情報源リンク
- 特になし (提供されたコミット情報と一般的なプログラミング知識に基づいています)