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

[インデックス 16521] ファイルの概要

このコミットは、Goランタイムのsymtab.cファイルにおけるメモリ割り当て方法の変更に関するものです。具体的には、シンボルテーブルのポインタマップ情報(func[nfunc-1].ptrs.array)の割り当てに、これまでのruntime·mallocgcではなくruntime·persistentallocを使用するように変更されています。この変更の目的は、ヒープサイズを削減することにあります。

コミット

commit 83445fdcc3639fc102c11aa1820d592a8615cc86
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Mon Jun 10 09:17:46 2013 +0400

    runtime: use persistentalloc instead of mallocgc in symtab
    Reduces heap size.
    
    R=golang-dev, khr
    CC=golang-dev
    https://golang.org/cl/10140043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/83445fdcc3639fc102c11aa1820d592a8615cc86

元コミット内容

src/pkg/runtime/symtab.c ファイルにおいて、func[nfunc-1].ptrs.array のメモリ割り当てが runtime·mallocgc から runtime·persistentalloc に変更されました。

変更前:

func[nfunc-1].ptrs.array = runtime·mallocgc(cap*sizeof(uint32), FlagNoPointers|FlagNoGC, 0, 1);

変更後:

func[nfunc-1].ptrs.array = runtime·persistentalloc(cap*sizeof(uint32), sizeof(uint32));

変更の背景

このコミットの主な背景は、Goプログラムのヒープサイズを削減することです。symtab(シンボルテーブル)は、Goバイナリに埋め込まれる重要なデバッグ情報やランタイム情報を含んでいます。この情報の一部であるポインタマップは、ガベージコレクション(GC)の際にポインタを正確に識別するために使用されます。

runtime·mallocgcはGoの一般的なヒープ割り当て関数であり、GCの対象となります。一方、runtime·persistentallocはGCの対象とならない、永続的なメモリ割り当てに使用される内部関数です。シンボルテーブルのようなランタイムのメタデータは、プログラムの実行中にその寿命が尽きることはなく、GCによって回収される必要がありません。むしろ、GCの対象とすることで、GCのオーバーヘッドやヒープの断片化を引き起こす可能性があります。

この変更により、シンボルテーブルのポインタマップ情報がGCの管理下から外れることで、GCの負荷が軽減され、結果としてGoプログラム全体のヒープサイズが削減されることが期待されます。

前提知識の解説

Goランタイム

Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクタ、スケジューラ、メモリ管理、プリミティブな同期メカニズムなどが含まれます。Goプログラムがコンパイルされると、ランタイムはバイナリに組み込まれ、プログラムの実行をサポートします。

シンボルテーブル (symtab)

シンボルテーブルは、コンパイラやリンカがプログラム内の識別子(変数名、関数名、型名など)とその属性(メモリ位置、型、スコープなど)を管理するために使用するデータ構造です。Goバイナリには、デバッグやプロファイリング、スタックトレースの生成などのために、このシンボル情報が埋め込まれています。Goのランタイムでは、pclntab(Program Counter Line Table)と呼ばれるテーブルが特に重要で、プログラムカウンタ(PC)とソースコードの行番号、関数名などをマッピングします。

ガベージコレクション (GC)

ガベージコレクションは、プログラムが動的に割り当てたメモリのうち、もはや使用されていない(到達不能な)領域を自動的に解放するプロセスです。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行して動作することで、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えています。

runtime·mallocgc

runtime·mallocgcは、GoランタイムがGoプログラムの一般的なヒープメモリ割り当てに使用する主要な関数です。Goのコードでmakenewを使ってメモリを割り当て、そのオブジェクトがヒープにエスケープする場合、内部的にはmallocgcが呼び出されます。この関数によって割り当てられたメモリは、Goのガベージコレクタによって管理され、到達不能になった時点で自動的に解放されます。

runtime·persistentalloc

runtime·persistentallocは、Goランタイム内部で使用される特殊なメモリ割り当て関数です。この関数によって割り当てられるメモリは、ガベージコレクタの管理対象外となります。これは、ランタイム自身のメタデータや、プログラムの全期間にわたって存在し続ける必要のあるデータ構造など、GCによって回収されるべきではない永続的なデータのために設計されています。persistentallocで割り当てられたメモリは、通常、明示的に解放されることはなく、プログラムの終了まで保持されます。

ポインタマップ

Goのガベージコレクタは、メモリ上のどの部分がポインタであり、どの部分がポインタでないかを正確に知る必要があります。これにより、GCは到達可能性を正しく判断し、誤って使用中のメモリを解放することを防ぎます。ポインタマップは、特定のデータ構造や関数のスタックフレーム内で、どのオフセットにポインタが存在するかを示す情報を提供します。これにより、GCは効率的かつ正確にポインタをスキャンできます。

技術的詳細

このコミットは、Goランタイムのメモリ管理戦略における最適化の一環です。symtab.cは、Goバイナリに埋め込まれるシンボルテーブルの処理に関連するコードを含んでいます。特に、dofunc関数内で関数のポインタマップ情報(func[nfunc-1].ptrs.array)を構築する際に、メモリ割り当てが行われます。

変更前は、このポインタマップの配列がruntime·mallocgcによって割り当てられていました。これは、通常のGoプログラムのデータと同じヒープ領域に割り当てられ、GCの対象となります。しかし、シンボルテーブルの情報はプログラムの実行中に変化することはなく、その寿命はプログラム全体の寿命と同じです。このような永続的なデータがGCの対象となるヒープに存在すると、以下のような問題が発生する可能性があります。

  1. GCのオーバーヘッド: GCはヒープ上のすべての到達可能なオブジェクトをスキャンし、マークする必要があります。シンボルテーブルのような大きな静的データがヒープにあると、GCのスキャン対象が増え、GCの実行時間が増加する可能性があります。
  2. ヒープサイズの増大: GCの対象となるメモリは、GCが管理するヒープ領域に存在します。シンボルテーブルがヒープに割り当てられると、その分だけヒープのサイズが増大し、メモリフットプリントが増加します。
  3. キャッシュ効率の低下: GCはメモリを移動させたり、再配置したりすることがあります。永続的なデータがGCによって移動される可能性があると、CPUキャッシュの効率が低下する可能性があります。

これらの問題を解決するため、このコミットではruntime·mallocgcruntime·persistentallocに置き換えています。persistentallocは、GCの対象外となるメモリ領域からメモリを割り当てます。これにより、シンボルテーブルのポインタマップはGCのスキャン対象から外れ、GCのオーバーヘッドが削減されます。また、ヒープサイズもその分だけ削減され、全体的なメモリ効率が向上します。

persistentallocは、割り当てられたメモリがGCによって移動されないことを保証するため、そのメモリへのポインタは安定しています。これは、ランタイムの内部構造にとって非常に重要です。

コアとなるコードの変更箇所

変更は src/pkg/runtime/symtab.c ファイルの dofunc 関数内で行われています。

--- a/src/pkg/runtime/symtab.c
+++ b/src/pkg/runtime/symtab.c
@@ -228,13 +228,12 @@ dofunc(Sym *sym)
 		else if(runtime·strcmp(sym->name, (byte*)\".args\") == 0)
 			func[nfunc-1].args = sym->value;
 		else if(runtime·strcmp(sym->name, (byte*)\".nptrs\") == 0) {
-			// TODO(cshapiro): use a dense representation for gc information
 			if(sym->value != func[nfunc-1].args/sizeof(uintptr)) {
 				runtime·printf("runtime: pointer map size and argument size disagree\\n");
 				runtime·throw("mangled symbol table");
 			}
 			cap = ROUND(sym->value, 32) / 32;
-			func[nfunc-1].ptrs.array = runtime·mallocgc(cap*sizeof(uint32), FlagNoPointers|FlagNoGC, 0, 1);\n
+			func[nfunc-1].ptrs.array = runtime·persistentalloc(cap*sizeof(uint32), sizeof(uint32));
 			func[nfunc-1].ptrs.len = 0;
 			func[nfunc-1].ptrs.cap = cap;
 		} else if(runtime·strcmp(sym->name, (byte*)\".ptrs\") == 0) {

コアとなるコードの解説

この変更は、dofunc関数内でシンボルテーブルを解析し、関数の情報を構築する部分にあります。

else if(runtime·strcmp(sym->name, (byte*)\".nptrs\") == 0) のブロックは、関数のポインタマップのサイズ情報(.nptrsシンボル)を処理する部分です。ここで、ポインタマップを格納するための配列 func[nfunc-1].ptrs.array のメモリが割り当てられます。

変更前は、以下の行でメモリが割り当てられていました。

func[nfunc-1].ptrs.array = runtime·mallocgc(cap*sizeof(uint32), FlagNoPointers|FlagNoGC, 0, 1);
  • runtime·mallocgc: 一般的なヒープ割り当て関数。
  • cap*sizeof(uint32): 割り当てるメモリのサイズ。capはポインタマップの容量、sizeof(uint32)は各エントリのサイズ。
  • FlagNoPointers|FlagNoGC: このフラグは、割り当てられたメモリがポインタを含まず、GCの対象外であることを示唆していますが、mallocgc自体はGCヒープから割り当てるため、このフラグはGCの動作に影響を与えるものの、根本的にGCの対象外にするわけではありません。

変更後は、以下の行に置き換えられました。

func[nfunc-1].ptrs.array = runtime·persistentalloc(cap*sizeof(uint32), sizeof(uint32));
  • runtime·persistentalloc: GCの対象外となる永続的なメモリを割り当てる関数。
  • cap*sizeof(uint32): 割り当てるメモリのサイズ。
  • sizeof(uint32): アライメントの指定。

この変更により、func[nfunc-1].ptrs.array がGCヒープではなく、GCの管理外の領域に割り当てられるようになります。これにより、シンボルテーブルのポインタマップデータがGCのスキャン対象から除外され、GCの効率が向上し、ヒープサイズが削減されます。

また、変更前のコードには // TODO(cshapiro): use a dense representation for gc information というコメントがありましたが、このコミットでは削除されています。これは、この変更がGC情報の表現方法の改善とは直接関係ないためか、あるいはこの変更によってそのTODOが不要になったためと考えられます。

関連リンク

  • Goのガベージコレクションに関する公式ドキュメントやブログ記事
  • Goのメモリ管理に関する詳細な技術記事

参考にした情報源リンク