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

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

このコミットは、Goランタイムのガベージコレクタ(GC)のパフォーマンス最適化を目的としています。具体的には、Goのメモリヒープを管理する mheap 構造体をGCの対象から除外することで、GCの不要なスキャン処理を削減し、GCサイクルを高速化します。mheap は多くのスパン(メモリブロック)の先頭へのポインタを含んでいますが、それ自体がGCが追跡すべき「興味深い」ポインタ(ユーザーデータへのポインタ)を含んでいるわけではないため、GCスキャンから除外しても問題がないと判断されました。

コミット

commit 2a6520c2d367020951648379d9df7228f8d7151c
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Fri Jul 19 17:47:40 2013 +0400

    runtime: hide mheap from GC
    It contains pointers to first blocks of lots of spans.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/11416046

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

https://github.com/golang/go/commit/2a6520c2d367020951648379d9df7228f8d7151c

元コミット内容

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -15,6 +15,8 @@ package runtime
 #include "race.h"
 #include "stack.h"
 
+// Mark mheap as 'no pointers', it does not contain interesting pointers but occupies ~45K.
+#pragma dataflag 16
 MHeap runtime·mheap;
 
 int32	runtime·checking;

変更の背景

Go言語のランタイムは、効率的なメモリ管理とガベージコレクション(GC)を非常に重視しています。このコミットが行われた2013年当時、GoのGCは主にマーク・アンド・スイープ方式を採用しており、プログラムが使用しているメモリ領域(ヒープ)を定期的にスキャンし、到達可能なオブジェクト(ライブオブジェクト)を特定し、到達不能なオブジェクト(デッドオブジェクト)が占めるメモリを解放していました。

mheap はGoランタイムのメモリ管理において中心的な役割を果たすデータ構造であり、Goプログラムが動的に確保するメモリ(ヒープメモリ)の全体を管理しています。具体的には、メモリを小さなブロック(スパン)に分割し、それらのスパンの割り当てや解放、そして空きスパンの管理などを行います。mheap 自身は、これらのスパンの先頭ブロックへのポインタを多数保持しています。

しかし、mheap が保持するポインタは、Goプログラムのユーザーが作成したオブジェクトへのポインタではなく、あくまでランタイム内部のメモリ管理のためのポインタです。GCの目的は、ユーザーが作成したライブオブジェクトを特定することであり、ランタイム内部の管理構造体自体は常にライブであるため、その内部を詳細にスキャンすることはGCにとって無駄なオーバーヘッドとなります。

このコミットの背景には、mheap が約45KBという比較的小さなサイズでありながら、その内部に多数のポインタが含まれているため、GCが mheap をスキャンする際に不要な処理時間が発生しているという問題意識がありました。mheap をGCのポインタスキャン対象から除外することで、GCの効率を向上させ、全体のパフォーマンスを改善することが狙いです。

前提知識の解説

Goランタイムのメモリ管理と mheap

Goランタイムは、独自のメモリ管理システムを持っています。これは、オペレーティングシステムから大きなメモリブロックを確保し、それをGoプログラムのニーズに合わせて細かく分割・管理するものです。この管理の中心となるのが mheap (Memory Heap) 構造体です。

mheap は、Goのヒープメモリ全体を抽象化し、以下のような機能を提供します。

  • スパンの管理: Goのメモリは「スパン (span)」と呼ばれる連続したメモリブロックの単位で管理されます。mheap はこれらのスパンの割り当て、解放、そして空きスパンのリストを管理します。
  • アリーナ (Arena): mheap は、OSから取得したメモリをアリーナと呼ばれる大きなチャンクに分割し、そのアリーナからスパンを切り出して使用します。
  • メモリ割り当て: プログラムが makenew などでメモリを要求すると、mheap は適切なサイズのスパンを見つけて割り当てます。

ガベージコレクション (GC) とポインタスキャン

Goのガベージコレクタは、主に「マーク・アンド・スイープ」アルゴリズムに基づいています。

  1. マークフェーズ: GCは、プログラムのルート(グローバル変数、スタック上の変数など)から到達可能なすべてのオブジェクトを「ライブ」としてマークします。このプロセスでは、オブジェクトが持つポインタをたどって、さらに到達可能なオブジェクトをマークしていきます。
  2. スイープフェーズ: マークされなかったオブジェクト(デッドオブジェクト)が占めるメモリを解放し、再利用可能な状態にします。

このマークフェーズにおいて、GCはメモリ上のポインタを識別し、そのポインタが指す先をたどる必要があります。もし、あるデータ構造がGCが追跡すべきポインタを含んでいないにもかかわらず、GCがそのデータ構造をスキャンしようとすると、それは無駄な処理時間となります。

pragma dataflag 16

Goランタイムのコードは、C言語に似た goc (Go C) という言語で書かれています。この言語には、Goコンパイラやランタイムに特定の指示を与えるための pragma ディレクティブが存在します。

#pragma dataflag は、特定のグローバル変数やデータセクションの特性をコンパイラに伝えるために使用されます。dataflag の後に続く数値は、その特性を示すビットフラグです。

このコミットで追加された dataflag 16 は、Goランタイムの内部的な定義において、そのデータセクションが「ポインタを含まない」ことを示します。つまり、GCがこのセクションをスキャンしても、ユーザーが作成したオブジェクトへのポインタは見つからないため、スキャンをスキップしても安全であるという指示になります。これにより、GCは mheap の内部構造を詳細に検査する手間を省くことができます。

技術的詳細

このコミットの技術的な核心は、Goランタイムのガベージコレクタが mheap 構造体をスキャンする際の挙動を変更することにあります。

GoのGCは、ヒープ上のオブジェクトをスキャンしてライブオブジェクトを特定する際に、そのオブジェクトがポインタを含んでいるかどうかを判断する必要があります。ポインタを含まないオブジェクトであれば、その内部を再帰的にスキャンする必要はありません。

mheapMHeap 型のグローバル変数として定義されています。この MHeap 構造体自体は、メモリ管理のための内部的なポインタ(例えば、空きスパンのリストへのポインタや、アリーナの管理構造体へのポインタなど)を多数含んでいます。しかし、これらのポインタは、GCがユーザープログラムのライブオブジェクトを追跡するために必要なポインタとは異なります。mheap 自体はランタイムの根幹をなす構造体であり、常にライブであるため、その内部のポインタをたどってユーザーオブジェクトの到達可能性を判断する必要はありません。

#pragma dataflag 16 ディレクティブを MHeap runtime·mheap; の定義の直前に追加することで、コンパイラは runtime·mheap 変数が配置されるデータセクションに「ポインタを含まない」というフラグを付与します。このフラグは、GCがヒープをスキャンする際に参照され、runtime·mheap の領域はポインタスキャンの対象から除外されるようになります。

これにより、GCは mheap の約45KBのメモリ領域をスキャンする時間を節約できます。これは、GCサイクルごとに繰り返される処理であるため、特に大規模なGoアプリケーションや、GCの頻度が高いアプリケーションにおいて、GCの一時停止時間(ストップ・ザ・ワールド)の短縮や、全体的なスループットの向上に寄与します。

この最適化は、Goランタイムが内部的にどのようにメモリを管理し、GCがどのように動作するかを深く理解しているからこそ可能なものです。mheap の内部構造がGCのポインタスキャンにとって「興味深いポインタ」を含まないという前提が重要であり、この前提が崩れると、GCがライブオブジェクトを見落とす可能性が出てきます。しかし、このコミットは、その前提が安全に満たされていることを確認した上で行われています。

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

変更は src/pkg/runtime/malloc.goc ファイルの1箇所のみです。

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -15,6 +15,8 @@ package runtime
 #include "race.h"
 #include "stack.h"
 
+// Mark mheap as 'no pointers', it does not contain interesting pointers but occupies ~45K.
+#pragma dataflag 16
 MHeap runtime·mheap;
 
 int32	runtime·checking;

コアとなるコードの解説

追加された2行は以下の通りです。

// Mark mheap as 'no pointers', it does not contain interesting pointers but occupies ~45K.
#pragma dataflag 16
  1. // Mark mheap as 'no pointers', it does not contain interesting pointers but occupies ~45K. これはコメントであり、この変更の意図を明確に説明しています。mheap を「ポインタなし」としてマークすること、そしてそれが「興味深いポインタ」を含まず、約45KBのメモリを占有していることを示しています。ここでいう「興味深いポインタ」とは、GCがライブオブジェクトを追跡するために必要な、ユーザーデータへのポインタを指します。

  2. #pragma dataflag 16 これが実際の機能的な変更です。MHeap runtime·mheap; の定義の直前にこの pragma ディレクティブを挿入することで、Goコンパイラに対して runtime·mheap 変数が配置されるデータセクションが、GCがスキャンすべきポインタを含まないことを指示しています。 dataflag 16 は、Goランタイムの内部的な定義において、このデータ領域がGCのポインタスキャンから除外されるべきであることを示す特定のビットフラグです。これにより、GCは mheap の内部をスキャンする手間を省き、GCの効率を向上させます。

この変更は、Goランタイムのメモリ管理とGCの内部動作に深く関わる低レベルな最適化であり、Goプログラムのユーザーからは直接見えない部分でのパフォーマンス改善に貢献します。

関連リンク

参考にした情報源リンク