[インデックス 14138] ファイルの概要
このコミットは、Go言語のリンカである5l
(cmd/5l
)におけるメモリ消費量の削減を目的としたものです。具体的には、Adr
構造体のフィールドの順序を変更することで、メモリのアライメントとパディングによる無駄を削減し、ヒープメモリの使用量を最適化しています。
コミット
commit 1e9f3085457eb911cb46a13e2766697bddd9d413
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Fri Oct 12 13:39:12 2012 +0800
cmd/5l: reorder some struct fields to reduce memory consumption
Valgrind Massif result when linking godoc:
On amd64:
old new -/+
mem_heap_B 185844612 175358047 -5.7%
mem_heap_extra_B 773404 773137 -0.0%
On 386/ARM:
old new -/+
mem_heap_B 141775701 131289941 -7.4%
mem_heap_extra_B 737011 736955 -0.0%
R=golang-dev, r, dave
CC=golang-dev
https://golang.org/cl/6655045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1e9f3085457eb911cb46a13e2766697bddd9d413
元コミット内容
cmd/5l
: メモリ消費量を削減するためにいくつかの構造体フィールドを並べ替える。
godoc
をリンクする際のValgrind Massifの結果:
amd64の場合:
old | new | -/+ | |
---|---|---|---|
mem_heap_B | 185844612 | 175358047 | -5.7% |
mem_heap_extra_B | 773404 | 773137 | -0.0% |
386/ARMの場合:
old | new | -/+ | |
---|---|---|---|
mem_heap_B | 141775701 | 131289941 | -7.4% |
mem_heap_extra_B | 737011 | 736955 | -0.0% |
レビュー担当者: golang-dev, r, dave CC: golang-dev 関連する変更リスト: https://golang.org/cl/6655045
変更の背景
このコミットの背景には、Go言語のツールチェイン、特にリンカ(5l
はgo tool link
の内部的な実装の一部)のメモリ効率を改善するという明確な目的があります。リンカは、プログラムのビルド時に大量のシンボル情報やアドレス情報を処理するため、多くのメモリを消費する傾向があります。特に大規模なプロジェクトや、godoc
のような多くの依存関係を持つツールをリンクする際には、そのメモリ使用量が顕著になります。
コミットメッセージに記載されているValgrind Massifのプロファイリング結果は、この最適化の必要性を示しています。Valgrind Massifはヒープメモリの使用状況を詳細に分析するツールであり、この結果から、構造体のフィールドの並べ替えによってヒープメモリの使用量が大幅に削減されることが確認されています(amd64で5.7%、386/ARMで7.4%)。これは、ビルド時間の短縮や、メモリが限られた環境でのビルドの成功に貢献します。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
- 構造体(Struct): 複数の異なる型のデータを一つにまとめた複合データ型です。Go言語では
struct
キーワードで定義されます。 - メモリのアライメント(Memory Alignment): コンピュータのプロセッサがメモリからデータを効率的に読み書きするために、特定のデータ型がメモリ上で特定のバイト境界に配置されるようにする規則です。例えば、4バイトの整数は4バイト境界に、8バイトのポインタは8バイト境界に配置されることが一般的です。これにより、プロセッサは一度のメモリアクセスでデータを取得でき、パフォーマンスが向上します。
- メモリのパディング(Memory Padding): メモリのアライメント要件を満たすために、構造体のフィールド間や構造体の末尾に挿入される未使用のバイトのことです。例えば、4バイト境界に配置されるべきフィールドの前に1バイトのフィールドがある場合、その間に3バイトのパディングが挿入されることがあります。このパディングはメモリを消費しますが、データアクセスを高速化するために必要です。
- Valgrind Massif: Valgrindは、メモリ管理エラーの検出やプロファイリングを行うためのオープンソースのツールスイートです。Massifはその一部で、プログラムのヒープメモリ使用量を詳細にプロファイリングし、どのコードがどれだけのメモリを割り当てているか、時間の経過とともにどのように変化するかを可視化します。これにより、メモリリークや過剰なメモリ使用の原因を特定するのに役立ちます。
- Go言語のリンカ(
cmd/5l
): Go言語のビルドプロセスにおいて、コンパイルされたオブジェクトファイル(.o
ファイル)を結合し、実行可能なバイナリを生成するツールです。5l
は、Goの初期のツールチェインにおけるx86-64アーキテクチャ(amd64)向けのリンカの名称でした。Goのツールチェインは、各アーキテクチャ(例:5
はx86-64、6
はARMなど)に対応するリンカを持っていました。
技術的詳細
このコミットの技術的な核心は、構造体フィールドの並べ替えによるメモリパディングの最適化です。
プロセッサは、メモリからデータを読み込む際に、特定のバイト境界(アライメント)に配置されたデータを効率的に処理します。例えば、64ビットシステムでは、8バイトのデータ(ポインタなど)は8バイトの倍数のアドレスに配置されることが理想的です。
構造体を定義する際、コンパイラは各フィールドをその型のアライメント要件に従って配置しようとします。もしフィールドの順序が適切でない場合、アライメント要件を満たすために、コンパイラはフィールド間に「パディング」と呼ばれる未使用のバイトを挿入します。このパディングはメモリを消費しますが、プログラムからはアクセスできません。
例として、以下のような構造体を考えます(Go言語の型サイズは一般的なものと仮定)。
struct Example1 {
byte b; // 1バイト
int32 i; // 4バイト
int64 l; // 8バイト
}
この構造体がメモリに配置される場合、以下のようなパディングが発生する可能性があります(8バイトアライメントを仮定):
b
(1バイト)- パディング (3バイト) -
i
を4バイト境界に配置するため i
(4バイト)- パディング (0バイト) -
l
は既に8バイト境界に配置可能 l
(8バイト)
合計で1バイト + 3バイト + 4バイト + 8バイト = 16バイトとなり、実データは13バイトですが、3バイトのパディングが発生します。
一方、フィールドをサイズが大きい順に並べ替えると、パディングを最小限に抑えることができます。
struct Example2 {
int64 l; // 8バイト
int32 i; // 4バイト
byte b; // 1バイト
}
この場合、メモリ配置は以下のようになります:
l
(8バイト)i
(4バイト)b
(1バイト)- パディング (3バイト) - 構造体全体のサイズを8バイトの倍数にするため(構造体のアライメント要件による)
合計で8バイト + 4バイト + 1バイト + 3バイト = 16バイトとなり、この例では同じサイズですが、より複雑な構造体ではパディングが大幅に削減されることがあります。特に、ポインタ(通常8バイト)と小さな型(1バイトや4バイト)が混在する場合に効果的です。
このコミットでは、src/cmd/5l/l.h
内のAdr
構造体において、Sym* gotype
(ポインタ、通常8バイト)とint32 offset2
(4バイト)を、char type
(1バイト)やchar reg
(1バイト)などの小さなフィールドの前に移動させています。これにより、これらの大きなフィールドが適切なアライメントで配置されやすくなり、その結果として発生するパディングが削減され、全体のメモリ消費量が減少したと考えられます。
Valgrind Massifの結果は、この最適化が実際にヒープメモリの使用量を削減したことを定量的に示しており、特にmem_heap_B
(ヒープに割り当てられたバイト数)が顕著に減少しています。これは、リンカが内部的にAdr
構造体のインスタンスを多数生成・使用しているため、個々の構造体のサイズ削減が全体として大きな効果をもたらしたことを意味します。
コアとなるコードの変更箇所
変更はsrc/cmd/5l/l.h
ファイル内のAdr
構造体の定義にあります。
--- a/src/cmd/5l/l.h
+++ b/src/cmd/5l/l.h
@@ -74,13 +74,12 @@ struct Adr
char* u0sbig;
} u0;
Sym* sym;
+ Sym* gotype;
+ int32 offset2; // argsize
char type;
- uchar index; // not used on arm, required by ld/go.c
char reg;
char name;
- int32 offset2; // argsize
char class;
- Sym* gotype;
};
#define offset u0.u0offset
具体的には、以下のフィールドの順序が変更されています。
Sym* gotype;
int32 offset2; // argsize
これらの2つのフィールドが、元の位置(char class;
の下)から、Sym* sym;
の直後、かつchar type;
の前に移動されています。
また、uchar index;
フィールドは削除されていますが、コミットメッセージの意図はメモリ消費量の削減のためのフィールドの並べ替えであり、この削除は直接的な並べ替えとは異なる変更です。しかし、// not used on arm, required by ld/go.c
というコメントから、このフィールドが特定のアーキテクチャで不要になったか、あるいは別の方法で処理されるようになったため、削除された可能性があります。この削除もまた、構造体のサイズ削減に貢献します。
コアとなるコードの解説
変更前のAdr
構造体は以下のようになっていました(関連部分のみ抜粋):
struct Adr {
// ...
Sym* sym;
char type;
uchar index; // not used on arm, required by ld/go.c
char reg;
char name;
int32 offset2; // argsize
char class;
Sym* gotype;
};
変更後のAdr
構造体は以下のようになりました:
struct Adr {
// ...
Sym* sym;
Sym* gotype; // 移動
int32 offset2; // 移動
char type;
// uchar index; // 削除
char reg;
char name;
char class;
// Sym* gotype; // 元の位置から移動
// int32 offset2; // 元の位置から移動
};
この変更の主な目的は、Sym* gotype
(ポインタ型)とint32 offset2
(32ビット整数型)という比較的サイズの大きいフィールドを、char type
、char reg
、char name
、char class
といった1バイトのフィールドよりも前に配置することです。
一般的なシステムでは、ポインタは8バイト、int32
は4バイトのアライメント要件を持つことが多いです。一方、char
は1バイトのアライメントです。
変更前は、Sym* sym
の後にchar type
が来ており、その後にint32 offset2
やSym* gotype
が配置されていました。これにより、char
フィールドの後に、より大きなアライメント要件を持つフィールドが続く場合、コンパイラはパディングを挿入してアライメントを調整する必要がありました。
例えば、char class
の後にSym* gotype
が来る場合、char class
が1バイトなので、Sym* gotype
(8バイト)を8バイト境界に配置するために7バイトのパディングが必要になる可能性があります。
変更後は、Sym* sym
(ポインタ)の直後にSym* gotype
(ポインタ)とint32 offset2
(4バイト)が配置されています。これにより、これらの大きなフィールドが連続して配置され、その後に小さなchar
フィールドが続く形になります。この順序は、コンパイラがパディングを最小限に抑えるのに有利に働きます。
uchar index;
の削除も、構造体全体のサイズを削減する効果があります。コメントにあるように、このフィールドがARMアーキテクチャで不要になったのであれば、削除は妥当な最適化です。
結果として、このフィールドの並べ替えと不要なフィールドの削除により、Adr
構造体のインスタンスあたりのメモリ消費量が削減され、Valgrind Massifのプロファイリング結果が示すように、リンカ全体のヒープメモリ使用量が大幅に減少しました。これは、Go言語のツールチェインの効率性を高めるための重要な最適化です。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語の変更リスト (Gerrit): https://go.googlesource.com/go/+/refs/heads/master/CONTRIBUTING.md#The_Change_List (変更リストの概念について)
- Valgrind Massifのドキュメント: https://valgrind.org/docs/manual/ms-manual.html
参考にした情報源リンク
- Memory Alignment and Padding Explained: https://www.geeksforgeeks.org/memory-alignment-and-padding/
- Structure padding in C: https://www.javatpoint.com/structure-padding-in-c
- Valgrind Massif: a heap profiler: https://valgrind.org/docs/manual/ms-manual.html
- Go tool link documentation (general information about the linker): https://pkg.go.dev/cmd/go#hdr-Compile_and_run_Go_program (Goの
go tool link
に関する一般的な情報) - Go言語のソースコード(
src/cmd/5l/l.h
の歴史を追う場合): https://github.com/golang/go/blob/master/src/cmd/5l/l.h (現在のファイル)