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

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

このコミットは、Go言語のガベージコレクタ (GC) の効率を向上させるため、ポインタを含まないデータ(no-pointers data)を専用のセクションに分離する変更を導入しています。これにより、GCがスキャンする必要のあるメモリ領域が減少し、GCの実行時間短縮と誤検出の削減に貢献します。

コミット

Author: Russ Cox rsc@golang.org Date: Sun Feb 19 03:19:52 2012 -0500

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

https://github.com/golang/go/commit/4e3f8e915fadd17b7caffaae273eddd3528ac080

元コミット内容

gc, ld: tag data as no-pointers and allocate in separate section

The garbage collector can avoid scanning this section, with
reduces collection time as well as the number of false positives.
Helps a little bit with issue 909, but certainly does not solve it.

R=ken2
CC=golang-dev
https://golang.org/cl/5671099

変更の背景

Go言語のガベージコレクタは、プログラムが使用しなくなったメモリを自動的に解放する役割を担っています。GCの効率は、プログラムの実行性能に直結する重要な要素です。特に、GCがメモリをスキャンする際、ポインタ(他のメモリ領域への参照)が含まれているかどうかを判断する必要があります。ポインタが含まれていないことが確実なデータ領域をGCがスキャンすることは、無駄な処理であり、GC時間の増加や、誤ってポインタではない値をポインタと認識してしまう「誤検出(false positives)」の原因となります。

このコミットの背景には、Goのガベージコレクタの性能改善という明確な目的があります。コミットメッセージにもあるように、「issue 909」に関連する改善の一環として行われました。issue 909は、Goのガベージコレクタがポインタではない整数値をポインタと誤認識し、その結果、不要なメモリを保持し続けてしまう問題("false positives")を扱っています。この変更は、その問題を完全に解決するものではないものの、ポインタを含まないデータ領域をGCのスキャン対象から除外することで、GCの負担を軽減し、誤検出の可能性を減らすことを目指しています。

前提知識の解説

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

ガベージコレクションは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されなくなった(到達不可能になった)ものを自動的に解放する仕組みです。これにより、プログラマは手動でのメモリ管理(malloc/freeなど)から解放され、メモリリークのリスクを低減できます。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGC処理を進めることで、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えるように設計されています。

ポインタとメモリレイアウト

コンピュータのメモリは、連続したバイト列としてアドレスが割り当てられています。プログラムは、変数やデータ構造をメモリ上に配置します。ポインタは、そのメモリ上の特定のアドレスを指し示す変数です。Go言語では、*T のように型にアスタリスクを付けることでポインタ型を宣言します。

プログラムが使用するメモリ領域は、その性質によっていくつかのセクションに分けられます。一般的なセクションには以下のようなものがあります。

  • .text (コードセクション): 実行可能な機械語コードが格納されます。
  • .rodata (読み取り専用データセクション): 定数や文字列リテラルなど、実行中に変更されない読み取り専用のデータが格納されます。ポインタは含まれません。
  • .data (初期化済みデータセクション): プログラムの開始時に初期値を持つグローバル変数や静的変数が格納されます。ポインタを含む可能性があります。
  • .bss (未初期化データセクション): プログラムの開始時に0で初期化されるグローバル変数や静的変数が格納されます。ポインタを含む可能性があります。

Goのガベージコレクタは、メモリ上のポインタを追跡することで、どのオブジェクトがまだ使用されているかを判断します。そのため、ポインタが含まれる可能性のあるデータセクションはGCのスキャン対象となります。

NOPTRRODATA

このコミットで導入される NOPTR (No Pointers) は、そのデータ領域にポインタが一切含まれていないことを示すフラグです。同様に、RODATA (Read-Only Data) は読み取り専用のデータであることを示します。

  • RODATA: 読み取り専用であるため、実行中に内容が変更されることはありません。通常、ポインタも含まれません。
  • NOPTR: 読み取り専用であるかどうかにかかわらず、ポインタが含まれていないことを保証します。これにより、GCはこれらの領域をスキャンする必要がなくなります。

これらのフラグは、コンパイラ (g コマンド群、例: 5g, 6g, 8g はそれぞれARM, AMD64, x86アーキテクチャ向けのGoコンパイラ) とリンカ (ld コマンド) の間で、データの特性を伝えるために使用されます。

技術的詳細

このコミットの主要な目的は、Goのガベージコレクタがポインタを含まないデータ領域を効率的にスキップできるようにすることです。これを実現するために、以下の技術的な変更が導入されています。

  1. 新しいデータセクション SNOPTRDATA の導入: リンカ (src/cmd/ld/lib.h で定義される enumSNOPTRDATA が追加) は、ポインタを含まないデータ専用の新しいセクション .noptrdata を作成します。これにより、従来の .data セクションからポインタを含まないデータを分離できます。

  2. コンパイラによる NOPTR フラグの付与: Goコンパイラ (src/cmd/5g/gsubr.c, src/cmd/6g/gsubr.c, src/cmd/8g/gsubr.c) は、グローバル変数や静的データがポインタを含まない型である場合 (!haspointers(nam->type))、そのデータに NOPTR フラグを付与するようになります。これは、ggloblnod 関数内で p->from.scale |= NOPTR; という形で実装されています。

  3. リンカによる SNOPTRDATA セクションへの配置: リンカ (src/cmd/ld/data.c, src/cmd/5l/obj.c, src/cmd/6l/obj.c, src/cmd/8l/obj.c) は、シンボルに NOPTR フラグが設定されている場合、そのデータを新しい SNOPTRDATA セクション(最終的には .noptrdata ELF/Mach-Oセクション)に配置するように変更されます。

    • addstring 関数では、文字列リテラルがデフォルトで SNOPTRDATA に配置されるようになります (s->type = SNOPTRDATA;)。文字列はポインタを含まないため、これは理にかなっています。
    • dodata 関数では、シンボルを処理するループが変更され、SNOPTRDATA タイプのシンボルが優先的に処理され、.noptrdata セクションに集約されます。その後、残りのデータが従来の .data セクションに配置されます。
    • address 関数では、.noptrdata セクションのアドレス範囲 (noptrdataenoptrdata) が定義され、Goランタイムがこの情報を利用できるようにします。
  4. GCによるスキャン対象の最適化: この変更により、Goランタイムのガベージコレクタは、.noptrdata セクションに配置されたデータがポインタを含まないことを認識し、その領域をスキャン対象から除外できるようになります。これにより、GCの実行時間が短縮され、ポインタの誤検出が減少します。

この変更は、Goのコンパイラ、リンカ、およびランタイムの連携によって実現される、低レベルかつ重要な最適化です。

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

このコミットにおける主要なコード変更は、以下のファイルに集中しています。

  • src/cmd/5g/gsubr.c, src/cmd/6g/gsubr.c, src/cmd/8g/gsubr.c (Goコンパイラ):

    • ggloblnod 関数において、グローバル変数や静的データがポインタを含まない場合 (!haspointers(nam->type))、NOPTR フラグを p->from.scale に追加する行が追加されました。
      if(nam->type != T && !haspointers(nam->type))
      	p->from.scale |= NOPTR;
      
  • src/cmd/5l/5.out.h, src/cmd/6l/6.out.h, src/cmd/8l/8.out.h (リンカのヘッダファイル):

    • NOPTR マクロが定義されました。
      #define RODATA	(1<<3)
      #define NOPTR	(1<<4) // 新規追加
      
  • src/cmd/5l/obj.c, src/cmd/6l/obj.c, src/cmd/8l/obj.c (リンカのオブジェクト処理):

    • loop 関数内で、シンボルのタイプが SNOPTRDATA でないことを確認する条件が追加されました。これにより、SNOPTRDATA タイプのシンボルが再定義チェックの対象外となります。
      if(s->type != SBSS && s->type != SNOPTRDATA && !s->dupok) {
      
    • p->from.scaleNOPTR フラグが設定されている場合、シンボルのタイプを SNOPTRDATA に設定するロジックが追加されました。
      else if(p->from.scale & NOPTR)
      	s->type = SNOPTRDATA;
      
  • src/cmd/ld/data.c (リンカのデータセクション処理):

    • addstring 関数で、文字列シンボルのデフォルトタイプが SDATA から SNOPTRDATA に変更されました。
      if(s->type == 0)
      	s->type = SNOPTRDATA; // 変更
      
    • dodata 関数内で、データセクションの処理ロジックが大幅に変更されました。
      • .noptrdata セクションが新しく追加され、SNOPTRDATA タイプのシンボルがこのセクションに配置されるようになりました。
      • 従来の .data セクションの前に .noptrdata セクションが配置されるように、セクションの生成とシンボルの配置順序が調整されました。
    • address 関数内で、.noptrdata セクションの仮想アドレス (noptrenoptrdata) が定義され、xdefine を通じてGoランタイムに公開されるようになりました。
      xdefine("noptrdata", SBSS, noptr->vaddr);
      xdefine("enoptrdata", SBSS, noptr->vaddr + noptr->len);
      
  • src/cmd/ld/lib.h (リンカのライブラリヘッダ):

    • SNOPTRDATAenum SymKind に追加されました。
      SNOPTRDATA, // 新規追加
      SDATA,
      
  • src/cmd/ld/symtab.c (リンカのシンボルテーブル処理):

    • symtab 関数内で、noptrdataenoptrdata シンボルが定義され、リンカによって生成されるシンボルテーブルにこれらのアドレスが記録されるようになりました。
      xdefine("noptrdata", SBSS, 0);
      xdefine("enoptrdata", SBSS, 0);
      

コアとなるコードの解説

この変更の核となるのは、Goのコンパイラとリンカが連携して、ポインタを含まないデータを識別し、それを専用のメモリセクションに配置する仕組みです。

  1. コンパイラ (gsubr.c): ggloblnod 関数は、グローバル変数や静的データのシンボルを処理する際に呼び出されます。ここで、haspointers(nam->type) という関数を使って、そのデータの型がポインタを含んでいるかどうかをチェックします。もしポインタを含まない型であれば、シンボル情報の一部である p->from.scaleNOPTR フラグを立てます。このフラグは、リンカに対する「このデータはポインタを含まない」という指示になります。

  2. リンカ (obj.c, data.c, lib.h, symtab.c):

    • lib.h: SNOPTRDATA という新しいシンボルタイプが定義されます。これは、ポインタを含まないデータが属するセクションを示すためのものです。
    • obj.c: リンカは、コンパイラから渡されたシンボル情報(p->from.scaleNOPTR フラグが立っているか)を読み取ります。もし NOPTR フラグが立っていれば、そのシンボルの内部的なタイプを SNOPTRDATA に設定します。
    • data.c: このファイルは、リンカが最終的な実行可能ファイルのデータセクションを構築するロジックを含んでいます。
      • addstring 関数では、文字列リテラルがデフォルトで SNOPTRDATA タイプとして扱われるようになります。文字列は通常ポインタを含まないため、これは妥当な変更です。
      • dodata 関数が最も重要な変更箇所です。ここでは、シンボルをイテレートし、SNOPTRDATA タイプのシンボルを優先的に集めて、新しい .noptrdata セクションを構築します。その後、残りの(ポインタを含む可能性のある)データが従来の .data セクションに配置されます。これにより、物理的にポインタを含まないデータとそうでないデータがメモリ上で分離されます。
      • address 関数では、リンカが生成する実行可能ファイル内の .noptrdata セクションの開始アドレスと終了アドレスを特定し、それらを noptrdata および enoptrdata というシンボルとして定義します。これらのシンボルは、Goランタイムが実行時に .noptrdata セクションの範囲を知るために使用されます。
    • symtab.c: noptrdataenoptrdata シンボルがリンカのシンボルテーブルに登録され、デバッグ情報やランタイムからのアクセスが可能になります。

この一連の変更により、Goのガベージコレクタは、実行時に noptrdataenoptrdata シンボルの値を見て、その範囲のメモリ領域はポインタを含まないためスキャンする必要がないと判断できるようになります。これにより、GCの効率が向上し、特に大きな静的データや文字列リテラルが多いプログラムにおいて、GCのオーバーヘッドが削減されます。

関連リンク

参考にした情報源リンク

  • Go言語のガベージコレクションに関する公式ドキュメントやブログ記事 (一般的なGo GCの仕組み理解のため)
  • ELFファイルフォーマットやメモリセクションに関する一般的な情報 (.text, .data, .rodata, .bss の理解のため)
  • Goのコンパイラとリンカの内部構造に関する資料 (Goのツールチェインがどのように動作するかを理解するため)
  • Goのランタイムとメモリ管理に関する資料 (GCがどのようにメモリをスキャンするかを理解するため)
  • Goのソースコード (特に src/cmd/gc, src/cmd/ld, src/runtime ディレクトリ)
  • GoのIssueトラッカー (特に issue 909 の詳細を理解するため)

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

このコミットは、Go言語のガベージコレクタ (GC) の効率を向上させるため、ポインタを含まないデータ(no-pointers data)を専用のメモリセクションに分離する変更を導入しています。これにより、GCがスキャンする必要のあるメモリ領域が減少し、GCの実行時間短縮と誤検出の削減に貢献します。

コミット

Author: Russ Cox rsc@golang.org Date: Sun Feb 19 03:19:52 2012 -0500

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

https://github.com/golang/go/commit/4e3f8e915fadd17b7caffaae273eddd3528ac080

元コミット内容

gc, ld: tag data as no-pointers and allocate in separate section

The garbage collector can avoid scanning this section, with
reduces collection time as well as the number of false positives.
Helps a little bit with issue 909, but certainly does not solve it.

R=ken2
CC=golang-dev
https://golang.org/cl/5671099

変更の背景

Go言語のガベージコレクタは、プログラムが使用しなくなったメモリを自動的に解放する役割を担っており、その効率はプログラムの実行性能に直結する重要な要素です。GCは、メモリ上のポインタを追跡することで、どのオブジェクトがまだ使用されているかを判断します。しかし、ポインタを含まないデータ領域(例えば、純粋な数値の配列や文字列リテラルなど)までGCがスキャンすることは、無駄な処理であり、GC時間の増加や、誤ってポインタではない値をポインタと認識してしまう「誤検出(false positives)」の原因となります。

このコミットの背景には、Goのガベージコレクタの性能改善という明確な目的があります。コミットメッセージにもあるように、「issue 909」に関連する改善の一環として行われました。issue 909は、Goのガベージコレクタがポインタではない整数値をポインタと誤認識し、その結果、不要なメモリを保持し続けてしまう問題("false positives")を扱っています。この変更は、その問題を完全に解決するものではないものの、ポインタを含まないデータ領域をGCのスキャン対象から除外することで、GCの負担を軽減し、誤検出の可能性を減らすことを目指しています。これにより、GCの実行時間が短縮され、全体的なアプリケーションのパフォーマンスが向上することが期待されます。

前提知識の解説

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

ガベージコレクションは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されなくなった(到達不可能になった)ものを自動的に解放する仕組みです。これにより、プログラマは手動でのメモリ管理(malloc/freeなど)から解放され、メモリリークのリスクを低減できます。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGC処理を進めることで、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えるように設計されています。

GoのGCは、"Tracing Garbage Collection"の一種であり、"roots"(グローバル変数やゴルーチンのスタックなど)から開始し、すべてのポインタをたどって他のオブジェクトを推移的にマークすることで「生きている」オブジェクトを識別します。これらのポインタをたどることで到達できないメモリは、ガベージと見なされ、回収されます。

ポインタとメモリレイアウト

コンピュータのメモリは、連続したバイト列としてアドレスが割り当てられています。プログラムは、変数やデータ構造をメモリ上に配置します。ポインタは、そのメモリ上の特定のアドレスを指し示す変数です。Go言語では、*T のように型にアスタリスクを付けることでポインタ型を宣言します。

プログラムが使用するメモリ領域は、その性質によっていくつかのセクションに分けられます。一般的なセクションには以下のようなものがあります。

  • .text (コードセクション): 実行可能な機械語コードが格納されます。
  • .rodata (読み取り専用データセクション): 定数や文字列リテラルなど、実行中に変更されない読み取り専用のデータが格納されます。通常、ポインタは含まれません。
  • .data (初期化済みデータセクション): プログラムの開始時に初期値を持つグローバル変数や静的変数が格納されます。ポインタを含む可能性があります。
  • .bss (未初期化データセクション): プログラムの開始時に0で初期化されるグローバル変数や静的変数が格納されます。ポインタを含む可能性があります。

Goのガベージコレクタは、メモリ上のポインタを追跡することで、どのオブジェクトがまだ使用されているかを判断します。そのため、ポインタが含まれる可能性のあるデータセクションはGCのスキャン対象となります。

NOPTRRODATA

このコミットで導入される NOPTR (No Pointers) は、そのデータ領域にポインタが一切含まれていないことを示すフラグです。同様に、RODATA (Read-Only Data) は読み取り専用のデータであることを示します。

  • RODATA: 読み取り専用であるため、実行中に内容が変更されることはありません。通常、ポインタも含まれません。
  • NOPTR: 読み取り専用であるかどうかにかかわらず、ポインタが含まれていないことを保証します。これにより、GCはこれらの領域をスキャンする必要がなくなります。

これらのフラグは、コンパイラ (g コマンド群、例: 5g, 6g, 8g はそれぞれARM, AMD64, x86アーキテクチャ向けのGoコンパイラ) とリンカ (ld コマンド) の間で、データの特性を伝えるために使用されます。

技術的詳細

このコミットの主要な目的は、Goのガベージコレクタがポインタを含まないデータ領域を効率的にスキップできるようにすることです。これを実現するために、以下の技術的な変更がGoのツールチェイン(コンパイラとリンカ)に導入されています。

  1. 新しいデータセクション SNOPTRDATA の導入: リンカの内部表現において、ポインタを含まないデータ専用の新しいシンボルタイプ SNOPTRDATA が定義されます (src/cmd/ld/lib.h に追加)。これにより、従来の SDATA (通常のデータ) とは異なるカテゴリとして、ポインタを含まないデータを識別できるようになります。最終的に、このタイプのデータは実行可能ファイル内の .noptrdata という専用のセクションに配置されます。

  2. コンパイラによる NOPTR フラグの付与: Goコンパイラ (src/cmd/5g/gsubr.c, src/cmd/6g/gsubr.c, src/cmd/8g/gsubr.c) は、グローバル変数や静的データがポインタを含まない型である場合 (!haspointers(nam->type))、そのデータに NOPTR フラグを付与するようになります。このフラグは、コンパイラがリンカに渡すシンボル情報の一部として埋め込まれます。具体的には、ggloblnod 関数内で、シンボルのfrom.scaleフィールドにNOPTRビットが設定されます。

  3. リンカによる SNOPTRDATA セクションへの配置: リンカ (src/cmd/5l/obj.c, src/cmd/6l/obj.c, src/cmd/8l/obj.c, src/cmd/ld/data.c) は、コンパイラから受け取ったシンボル情報に基づいて、データを適切なセクションに配置します。

    • シンボルに NOPTR フラグが設定されている場合、リンカはそのシンボルの内部タイプを SNOPTRDATA に設定します。
    • src/cmd/ld/data.caddstring 関数では、文字列リテラルがデフォルトで SNOPTRDATA に配置されるようになります。文字列はポインタを含まないため、これはGC効率化に貢献します。
    • dodata 関数では、シンボルを処理するロジックが変更され、SNOPTRDATA タイプのシンボルが優先的に処理され、.noptrdata セクションに集約されます。その後、残りの(ポインタを含む可能性のある)データが従来の .data セクションに配置されます。これにより、実行可能ファイル内のメモリレイアウトが最適化され、ポインタを含まないデータが連続した領域にまとめられます。
    • address 関数では、リンカが最終的な実行可能ファイル内の .noptrdata セクションの開始アドレスと終了アドレスを特定し、それらを noptrdata および enoptrdata というシンボルとして定義します。これらのシンボルは、Goランタイムが実行時に .noptrdata セクションの範囲を知るために使用されます。
  4. GCによるスキャン対象の最適化: この変更により、Goランタイムのガベージコレクタは、実行時に noptrdata および enoptrdata シンボルの値(つまり、.noptrdata セクションのメモリ範囲)を参照できるようになります。GCは、この範囲のメモリ領域がポインタを含まないことを認識するため、その領域をスキャン対象から除外できます。これにより、GCの実行時間が短縮され、ポインタの誤検出が減少します。特に、大量の静的データや文字列リテラルを持つアプリケーションにおいて、GCのオーバーヘッドが大幅に削減される効果が期待されます。

この一連の変更は、Goのコンパイラ、リンカ、およびランタイムの密接な連携によって実現される、低レベルかつ重要な最適化であり、Goプログラムの全体的な性能向上に寄与します。

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

このコミットにおける主要なコード変更は、Goのコンパイラとリンカの以下のファイルに集中しています。

  • src/cmd/5g/gsubr.c, src/cmd/6g/gsubr.c, src/cmd/8g/gsubr.c (Goコンパイラ):

    • ggloblnod 関数において、グローバル変数や静的データがポインタを含まない型である場合 (!haspointers(nam->type))、そのシンボル情報の一部である p->from.scaleNOPTR フラグを追加する行が挿入されました。
      if(nam->type != T && !haspointers(nam->type))
      	p->from.scale |= NOPTR;
      
  • src/cmd/5l/5.out.h, src/cmd/6l/6.out.h, src/cmd/8l/8.out.h (リンカのヘッダファイル):

    • リンカが使用するフラグとして、NOPTR マクロが新しく定義されました。
      #define RODATA	(1<<3)
      #define NOPTR	(1<<4) // 新規追加
      
  • src/cmd/5l/obj.c, src/cmd/6l/obj.c, src/cmd/8l/obj.c (リンカのオブジェクト処理):

    • loop 関数内で、シンボルの再定義チェックにおいて、SNOPTRDATA タイプのシンボルが除外されるように条件が変更されました。
      if(s->type != SBSS && s->type != SNOPTRDATA && !s->dupok) {
      
    • p->from.scaleNOPTR フラグが設定されている場合、シンボルのタイプを SNOPTRDATA に設定するロジックが追加されました。
      else if(p->from.scale & NOPTR)
      	s->type = SNOPTRDATA;
      
  • src/cmd/ld/data.c (リンカのデータセクション処理):

    • addstring 関数で、文字列シンボルのデフォルトタイプが SDATA から SNOPTRDATA に変更されました。
      if(s->type == 0)
      	s->type = SNOPTRDATA; // 変更
      
    • dodata 関数内で、データセクションの構築ロジックが大幅に変更されました。
      • .noptrdata セクションが新しく追加され、SNOPTRDATA タイプのシンボルがこのセクションに配置されるようになりました。
      • 従来の .data セクションの前に .noptrdata セクションが配置されるように、セクションの生成とシンボルの配置順序が調整されました。
    • address 関数内で、.noptrdata セクションの仮想アドレス (noptrenoptrdata) が定義され、xdefine を通じてGoランタイムに公開されるようになりました。
      xdefine("noptrdata", SBSS, noptr->vaddr);
      xdefine("enoptrdata", SBSS, noptr->vaddr + noptr->len);
      
  • src/cmd/ld/lib.h (リンカのライブラリヘッダ):

    • リンカが使用するシンボルタイプを定義する enumSNOPTRDATA が追加されました。
      SNOPTRDATA, // 新規追加
      SDATA,
      
  • src/cmd/ld/symtab.c (リンカのシンボルテーブル処理):

    • symtab 関数内で、noptrdataenoptrdata シンボルが定義され、リンカによって生成されるシンボルテーブルにこれらのアドレスが記録されるようになりました。
      xdefine("noptrdata", SBSS, 0);
      xdefine("enoptrdata", SBSS, 0);
      

コアとなるコードの解説

この変更の核となるのは、Goのコンパイラとリンカが連携して、ポインタを含まないデータを識別し、それを専用のメモリセクションに配置する仕組みです。

  1. コンパイラ (gsubr.cggloblnod 関数): ggloblnod 関数は、Goのソースコードからグローバル変数や静的データが検出された際に呼び出され、それらのシンボル情報を構築します。この関数内で、haspointers(nam->type) というヘルパー関数が使用され、対象のデータの型がポインタを含んでいるかどうかをチェックします。もし、そのデータがポインタを一切含まない型であると判断された場合(例: 整数、浮動小数点数、文字列、ポインタを含まない構造体など)、リンカに渡すシンボル情報の一部である p->from.scale フィールドに NOPTR フラグ(ビットマスク)を立てます。このフラグは、リンカに対する「このデータはポインタを含まないため、GCのスキャン対象から除外できる」という明確な指示となります。

  2. リンカ (obj.c, data.c, lib.h, symtab.c):

    • lib.h: リンカの内部でデータを分類するために使用される enum に、新しく SNOPTRDATA というシンボルタイプが追加されます。これは、ポインタを含まないデータが属するセクションを示すためのものです。
    • obj.c: リンカは、コンパイラから渡されたシンボル情報(特に p->from.scaleNOPTR フラグが立っているか)を読み取ります。もし NOPTR フラグが立っていれば、そのシンボルの内部的なタイプを SNOPTRDATA に設定します。これにより、リンカは後続の処理でこのデータを特別に扱うことができます。
    • data.c: このファイルは、リンカが最終的な実行可能ファイルのデータセクションを構築する際の中心的なロジックを含んでいます。
      • addstring 関数では、文字列リテラルがデフォルトで SNOPTRDATA タイプとして扱われるように変更されます。文字列は通常、それ自体がポインタを含むことはなく、その内容もポインタではないため、この変更はGCの効率化に直接貢献します。
      • dodata 関数がこのコミットの最も重要な変更箇所です。この関数は、すべてのデータシンボルをイテレートし、それらを適切なメモリセクションに配置します。変更後、dodata はまず SNOPTRDATA タイプのシンボルを優先的に集め、新しい .noptrdata セクションを構築します。このセクションは、実行可能ファイル内でポインタを含まないデータが連続して配置される領域となります。その後、残りの(ポインタを含む可能性のある)データが従来の .data セクションに配置されます。これにより、物理的にポインタを含まないデータとそうでないデータがメモリ上で分離され、GCがスキャンすべき領域を明確に区別できるようになります。
      • address 関数では、リンカが生成する実行可能ファイル内の .noptrdata セクションの開始アドレスと終了アドレスを特定し、それらを noptrdata および enoptrdata というシンボルとして定義します。これらのシンボルは、Goランタイムが実行時に .noptrdata セクションの正確な範囲を知るために使用されます。
    • symtab.c: noptrdataenoptrdata シンボルがリンカのシンボルテーブルに登録され、デバッグ情報やランタイムからのアクセスが可能になります。

この一連の変更により、Goのガベージコレクタは、実行時に noptrdataenoptrdata シンボルの値を見て、その範囲のメモリ領域はポインタを含まないためスキャンする必要がないと判断できるようになります。これにより、GCの効率が向上し、特に大きな静的データや文字列リテラルが多いプログラムにおいて、GCのオーバーヘッドが削減されます。

関連リンク

参考にした情報源リンク

  • Go言語のガベージコレクションに関する公式ドキュメントやブログ記事
  • ELFファイルフォーマットやメモリセクションに関する一般的な情報
  • Goのコンパイラとリンカの内部構造に関する資料
  • Goのランタイムとメモリ管理に関する資料
  • Goのソースコード (特に src/cmd/gc, src/cmd/ld, src/runtime ディレクトリ)
  • GoのIssueトラッカー (特に issue 909 の詳細)
  • Web検索: "Go garbage collector no-pointers data section"