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

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

このコミットは、Go言語の標準ライブラリにsync.Pool型を導入するものです。sync.Poolは、一時的なオブジェクトの再利用を目的としたプール機構を提供し、ガベージコレクション(GC)の負荷を軽減することで、アプリケーションのパフォーマンス向上に寄与します。特に、頻繁に生成・破棄されるが、そのライフサイクルが短いオブジェクト(例: バイトスライス、バッファなど)の管理に有効です。

コミット

commit 8c6ef061e3c189ac3ac90a451d5680aab9d142618
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Dec 18 11:08:34 2013 -0800

    sync: add Pool type
    
    Adds the Pool type and docs, and use it in fmt.
    This is a temporary implementation, until Dmitry
    makes it fast.
    
    Uses the API proposal from Russ in http://goo.gl/cCKeb2 but
    adds an optional New field, as used in fmt and elsewhere.
    Almost all callers want that.
    
    Update #4720
    
    R=golang-dev, rsc, cshapiro, iant, r, dvyukov, khr
    CC=golang-dev
    https://golang.org/cl/41860043

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

https://github.com/golang/go/commit/8c6ef061e3c189ac3ac90a451d5680aab9d142618

元コミット内容

このコミットは、syncパッケージにPool型を追加し、そのドキュメントを整備しています。また、fmtパッケージでのPoolの使用例も含まれています。コミットメッセージには、これが一時的な実装であり、Dmitry Vyukovによる高速化が予定されていることが明記されています。

APIの設計は、Russ Coxによる提案(http://goo.gl/cCKeb2)に基づいていますが、fmtパッケージなどで必要とされるオプションのNewフィールドが追加されています。これは、ほとんどの利用者が、プールが空の場合に新しい値を生成する機能が必要であるためです。

この変更は、Go issue #4720に関連しています。

変更の背景

Go言語のガベージコレクションは非常に効率的ですが、短命なオブジェクトが大量に生成されるようなシナリオでは、GCのオーバーヘッドが無視できない問題となることがあります。特に、ネットワークプログラミングやI/O処理において、バッファや構造体などのオブジェクトが頻繁にアロケート・デアロケートされる場合、GCが頻繁に実行され、アプリケーションのレイテンシやスループットに影響を与える可能性があります。

sync.Poolの導入は、このような短命なオブジェクトの再利用を促進し、GCの負担を軽減することを目的としています。オブジェクトをプールに格納し、必要に応じて取り出すことで、オブジェクトの新規アロケーションを減らし、GCの実行回数や時間を削減することができます。これにより、アプリケーションのパフォーマンスが向上し、より安定した動作が期待されます。

コミットメッセージにある「This is a temporary implementation, until Dmitry makes it fast.」という記述は、この時点でのsync.Poolの実装が、機能的な側面を優先し、パフォーマンス最適化は後続の作業に委ねられていることを示唆しています。これは、Go言語の開発プロセスにおいて、まず基本的な機能を提供し、その後にパフォーマンスの改善を行うというアプローチが取られることがある典型的な例です。

また、「Uses the API proposal from Russ in http://goo.gl/cCKeb2 but adds an optional New field」という記述は、API設計の経緯を示しています。Russ Coxによる初期の提案をベースにしつつも、実際の利用シナリオ(特にfmtパッケージなど)を考慮して、プールが空の場合に新しいオブジェクトを生成するためのNewフィールドが追加されたことがわかります。これは、APIが単なる概念的な提案だけでなく、実際のコードベースでの利用を通じて洗練されていく過程を示しています。

前提知識の解説

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

Go言語は、自動メモリ管理(ガベージコレクション)を採用しています。これにより、開発者は手動でメモリを解放する手間から解放されます。GoのGCは、並行マーク&スイープ方式をベースにしており、アプリケーションの実行と並行して動作することで、STW(Stop The World)時間を最小限に抑えるように設計されています。しかし、大量のオブジェクトが頻繁に生成・破棄される場合、GCが頻繁にトリガーされ、CPUリソースを消費し、アプリケーションのパフォーマンスに影響を与える可能性があります。

オブジェクトプールパターン

オブジェクトプールパターンは、リソースの生成コストが高い場合に、あらかじめオブジェクトを生成しておき、必要に応じて再利用するデザインパターンです。これにより、オブジェクトの生成・破棄にかかるオーバーヘッドを削減し、パフォーマンスを向上させることができます。sync.Poolは、このオブジェクトプールパターンをGo言語の並行処理環境に適した形で提供するものです。

interface{}

Go言語のinterface{}型は、任意の型の値を保持できる空のインターフェースです。sync.Poolは、このinterface{}型を使用して、様々な型のオブジェクトをプールに格納できるように設計されています。これにより、ジェネリックなオブジェクトプールとして機能します。

sync.Mutex

sync.Mutexは、Go言語における相互排他ロック(ミューテックス)を提供する型です。複数のGoroutineが共有リソースに同時にアクセスする際に、データ競合を防ぐために使用されます。sync.Poolの内部では、複数のGoroutineからのPutGet操作が安全に行われるように、sync.Mutexが使用されています。

runtime.SetFinalizer

runtime.SetFinalizerは、Go言語のランタイム関数で、オブジェクトがガベージコレクタによって回収される直前に実行される関数(ファイナライザ)を設定するために使用されます。sync.Poolのテストコードでは、プールされたオブジェクトがGCによって適切に回収されることを検証するために、このファイナライザが利用されています。

atomicパッケージ

sync/atomicパッケージは、アトミック操作(不可分操作)を提供します。これにより、複数のGoroutineから共有変数にアクセスする際に、ロックを使用せずに安全に値を操作することができます。sync.Poolのテストコードでは、atomic.AddUint32などが使用されており、並行処理環境での正確なカウンタの実装に役立っています。

技術的詳細

sync.Poolは、一時的なオブジェクトを効率的に再利用するためのメカニズムを提供します。その主要な特徴と実装の詳細は以下の通りです。

  1. GCによるクリア: sync.Poolに格納されたオブジェクトは、GCが実行されるたびに自動的にクリアされます。これは、src/pkg/runtime/mgc0.cclearpools関数とruntime·gc関数によって実現されています。GCが開始されると、clearpoolsが呼び出され、登録されているすべてのプールからオブジェクトが削除されます。これにより、プールされたオブジェクトがGCの対象となり、メモリが解放される可能性があります。この挙動は、sync.Poolが「一時的な」オブジェクトのプールであることを強調しています。つまり、プールされたオブジェクトは永続的に保持されることを保証されず、GCによっていつでも破棄される可能性があります。

  2. Putメソッド: Putメソッドは、オブジェクトをプールに追加します。

    • p.mu.Lock()p.mu.Unlock()によって、複数のGoroutineからの同時アクセスが保護されています。
    • p.listという[]interface{}スライスにオブジェクトが追加されます。これは、プールされたオブジェクトを保持するための内部的なストレージです。
    • runtime_registerPool(p)が呼び出され、プールがGoランタイムに登録されます。これにより、GC時にプールがクリアされる対象となります。この登録は、プールが初めて使用される際(p.listnilの場合)に一度だけ行われます。
  3. Getメソッド: Getメソッドは、プールからオブジェクトを取得します。

    • p.mu.Lock()p.mu.Unlock()によって、複数のGoroutineからの同時アクセスが保護されています。
    • p.listスライスの末尾からオブジェクトを取り出します。これは、LIFO(Last-In, First-Out)のような挙動を示します。
    • 取り出した要素はnilに設定され、スライスは短縮されます。これは、参照を確実に解放し、GCがオブジェクトを回収できるようにするためです。
    • プールが空の場合(x == nil)、かつp.Newフィールドが設定されている場合、p.New()関数が呼び出され、新しいオブジェクトが生成されて返されます。これにより、プールが空であっても常に有効なオブジェクトを取得できる保証が提供されます。
  4. Newフィールド: Pool構造体には、オプションのNewフィールドがあります。これは、プールが空のときにGetメソッドが呼び出された場合に、新しいオブジェクトを生成するための関数を指定します。この機能は、プールが空の場合にnilを返すのではなく、常に有効なオブジェクトを提供したい場合に非常に便利です。

  5. ランタイムとの連携: sync.Poolは、Goランタイムのガベージコレクタと密接に連携しています。src/pkg/runtime/mgc0.cの変更は、GCが実行されるたびに登録されたすべてのプールをクリアするメカニズムを導入しています。

    • poolsというグローバルな構造体が導入され、すべてのsync.Poolインスタンスがリンクリストとして管理されます。
    • sync·runtime_registerPool関数は、新しいsync.Poolインスタンスをこのリンクリストに登録します。
    • clearpools関数は、GCの開始時にこのリンクリストを走査し、各プールの内部スライス(p[1]p[2]p[3]はそれぞれlistスライス、muミューテックス、Newフィールドに対応すると考えられる)をnilに設定することで、プールされたオブジェクトへの参照を解除します。これにより、オブジェクトがGCの対象となります。

このGCによるクリアの挙動は、sync.Poolがキャッシュとしてではなく、一時的なオブジェクトの再利用メカニズムとして設計されていることを示しています。つまり、プールされたオブジェクトは、GCサイクルをまたいで永続的に保持されることを期待すべきではありません。

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

このコミットにおける主要なコード変更は以下の3つのファイルにわたります。

  1. src/pkg/runtime/mgc0.c:

    • poolsというグローバルな構造体(Lockheadを持つ)が追加されました。これは、Goランタイムが管理するすべてのsync.Poolインスタンスのリンクリストのヘッドを保持します。
    • sync·runtime_registerPool関数が追加されました。この関数は、Goのsync.Poolから呼び出され、新しいプールインスタンスをランタイムのpoolsリンクリストに登録します。
    • clearpools関数が追加されました。この関数は、poolsリンクリストを走査し、各プールの内部状態(特にlistスライス)をクリアすることで、プールされたオブジェクトへの参照を解除します。
    • runtime·gc関数内で、GCの開始時にclearpools()が呼び出されるように変更されました。これにより、GCが実行されるたびにすべてのsync.Poolがクリアされるようになります。
  2. src/pkg/sync/pool.go:

    • Pool構造体が新しく定義されました。この構造体は、以下のフィールドを持ちます。
      • next *Pool: ランタイムがプールをリンクリストで管理するために使用するフィールド(Goランタイムからのみアクセスされる)。
      • list []interface{}: プールされたオブジェクトを保持するスライス。
      • mu Mutex: listスライスへのアクセスを保護するためのミューテックス。
      • New func() interface{}: プールが空の場合に新しいオブジェクトを生成するためのオプションの関数。
    • runtime_registerPool(*Pool)という外部関数宣言が追加されました。これは、Goランタイムのsync·runtime_registerPool関数をGoコードから呼び出すためのものです。
    • Put(x interface{})メソッドが追加されました。これは、オブジェクトをプールに追加するロジックを実装しています。
    • Get() interface{}メソッドが追加されました。これは、プールからオブジェクトを取得するロジックを実装しており、必要に応じてNew関数を呼び出します。
  3. src/pkg/sync/pool_test.go:

    • TestPool関数: PutGetの基本的な動作、およびGC後のプールのクリア挙動をテストします。
    • TestPoolNew関数: Newフィールドが正しく機能し、プールが空の場合に新しいオブジェクトを生成することを確認します。
    • TestPoolGC関数: runtime.SetFinalizerを使用して、プールから取り出されたオブジェクトがGCによって適切に回収されることを検証します。これは、sync.Poolがオブジェクトへの強い参照を保持しないことを確認するための重要なテストです。
    • TestPoolStress関数: 複数のGoroutineが同時にPutGetを呼び出すストレスシナリオをテストし、並行処理環境でのsync.Poolの堅牢性を確認します。
    • BenchmarkPool関数: sync.Poolのパフォーマンスを測定するためのベンチマークテストです。

コアとなるコードの解説

src/pkg/runtime/mgc0.cの変更

このファイルはGoランタイムのガベージコレクタの一部です。sync.PoolがGCと連携するための重要な変更が含まれています。

static struct
{
	Lock;  
	void* head;
} pools;

void
sync·runtime_registerPool(void **p)
{
	runtime·lock(&pools);
	p[0] = pools.head;
	pools.head = p;
	runtime·unlock(&pools);
}

static void
clearpools(void)
{
	void **p, **next;

	for(p = pools.head; p != nil; p = next) {
		next = p[0];
		p[0] = nil; // next
		p[1] = nil; // slice
		p[2] = nil;
		p[3] = nil;
	}
	pools.head = nil;
}

// ... (runtime·gc関数内)
	clearpools();
  • pools構造体は、Goランタイムが管理するすべてのsync.Poolインスタンスのリンクリストのヘッドを保持します。Lockは、このリンクリストへのアクセスを保護するためのものです。
  • sync·runtime_registerPoolは、Goのsync.Poolから呼び出され、引数p(これはsync.Pool構造体へのポインタ)をpoolsリンクリストの先頭に追加します。p[0]sync.Pool構造体の最初のフィールドであるnext *Poolに対応します。
  • clearpools関数は、GCが開始されるたびに呼び出されます。この関数はpoolsリンクリストを走査し、各プールの内部状態をクリアします。具体的には、p[0]nextポインタ)、p[1]listスライス)、p[2]muミューテックス)、p[3]Newフィールド)をnilに設定します。これにより、プールされたオブジェクトへの参照が解除され、GCの対象となります。
  • runtime·gc関数内でclearpools()が呼び出されることで、GCサイクルごとにプールがリセットされることが保証されます。

src/pkg/sync/pool.goの変更

このファイルはsync.Pool型の定義と、そのPutおよびGetメソッドの実装を含みます。

type Pool struct {
	next *Pool         // for use by runtime. must be first.
	list []interface{} // offset known to runtime
	mu   Mutex         // guards list

	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	// It may not be changed concurrently with calls to Get.
	New func() interface{}
}

func runtime_registerPool(*Pool)

func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	p.mu.Lock()
	if p.list == nil {
		runtime_registerPool(p)
	}
	p.list = append(p.list, x)
	p.mu.Unlock()
}

func (p *Pool) Get() interface{} {
	p.mu.Lock()
	var x interface{}
	if n := len(p.list); n > 0 {
		x = p.list[n-1]
		p.list[n-1] = nil // Just to be safe
		p.list = p.list[:n-1]
	}
	p.mu.Unlock()
	if x == nil && p.New != nil {
		x = p.New()
	}
	return x
}
  • Pool構造体のnextフィールドは、Goランタイムが内部的にプールをリンクリストで管理するために使用されます。このフィールドが構造体の最初のフィールドであることは、C言語側のp[0]アクセスと整合性を保つために重要です。
  • listフィールドは、interface{}型のスライスであり、プールされたオブジェクトを実際に保持します。
  • muフィールドは、listスライスへのアクセスを保護するためのミューテックスです。
  • Newフィールドは、プールが空の場合に新しいオブジェクトを生成するための関数をオプションで指定できます。
  • Putメソッドでは、オブジェクトをlistスライスに追加する前に、p.list == nilの場合にruntime_registerPool(p)を呼び出して、このプールをランタイムに登録します。これにより、GC時にこのプールがクリアされる対象となります。
  • Getメソッドでは、listスライスの末尾からオブジェクトを取得します。取得後、そのスロットをnilに設定することで、参照を解除し、GCがオブジェクトを回収できるようにします。プールが空で、かつNew関数が設定されている場合は、New関数を呼び出して新しいオブジェクトを生成します。

src/pkg/sync/pool_test.goの変更

このファイルは、sync.Poolの機能と挙動を検証するためのテストケースを含みます。特に注目すべきは、GCとの連携をテストする部分です。

func TestPool(t *testing.T) {
	// disable GC so we can control when it happens.
	defer debug.SetGCPercent(debug.SetGCPercent(-1))
	var p Pool
	// ...
	p.Put("c")
	debug.SetGCPercent(100) // to allow following GC to actually run
	runtime.GC()
	if g := p.Get(); g != nil {
		t.Fatalf("got %#v; want nil after GC", g)
	}
}

func TestPoolGC(t *testing.T) {
	var p Pool
	var fin uint32
	const N = 100
	for i := 0; i < N; i++ {
		v := new(int)
		runtime.SetFinalizer(v, func(vv *int) {
			atomic.AddUint32(&fin, 1)
		})
		p.Put(v)
	}
	for i := 0; i < N; i++ {
		p.Get()
	}
	for i := 0; i < 5; i++ {
		runtime.GC()
		time.Sleep(time.Millisecond)
		// 1 pointer can remain on stack or elsewhere
		if atomic.LoadUint32(&fin) >= N-1 {
			return
		}
	}
	t.Fatalf("only %v out of %v resources are finalized",
		atomic.LoadUint32(&fin), N)
}
  • TestPoolでは、debug.SetGCPercent(-1)でGCを一時的に無効にし、runtime.GC()を明示的に呼び出すことで、GCがsync.Poolに与える影響を制御してテストしています。GC後にプールがクリアされ、Getnilを返すことを確認しています。
  • TestPoolGCでは、runtime.SetFinalizerを使用して、プールされたオブジェクトがGCによって実際に回収されることを検証しています。これは、sync.Poolがオブジェクトへの強い参照を保持せず、GCの対象となることを保証するための重要なテストです。N個のオブジェクトをプールに入れ、すべて取り出した後、GCを複数回実行し、ファイナライザが呼び出された回数をatomic.AddUint32でカウントしています。これにより、ほとんどすべてのオブジェクトがファイナライズされることを確認しています。

これらのコード変更は、sync.PoolがGoランタイムのGCと連携し、一時的なオブジェクトの再利用を効率的に行うための基盤を確立していることを示しています。

関連リンク

  • Go issue #4720: sync.Poolの導入に関連する議論のトラッキングイシュー。
  • Russ CoxによるAPI提案: http://goo.gl/cCKeb2 (Go issue #4720の議論内で参照されている可能性が高い)
  • Go CL 41860043: このコミットに対応するGoのコードレビューシステム(Gerrit)上のチェンジリスト。

参考にした情報源リンク

  • Go言語の公式ドキュメント: sync.Poolに関する最新の情報や使用例は、Go言語の公式ドキュメントを参照するのが最も正確です。
  • Go言語のソースコード: src/sync/pool.gosrc/runtime/mgc.go(または関連するGCファイル)のソースコードは、sync.Poolの内部動作を深く理解するための究極の情報源です。
  • Go言語のブログやカンファレンス発表: sync.Poolの設計思想やパフォーマンスに関する情報は、Go言語の公式ブログやGopherConなどのカンファレンス発表で解説されていることがあります。
  • Go issue #4720の議論: このイシューのコメントスレッドには、sync.Poolの設計に関する詳細な議論や、初期の懸念事項、解決策などが含まれている可能性があります。
  • Go言語のガベージコレクションに関する資料: GoのGCの仕組みを理解することは、sync.PoolがGCとどのように連携し、パフォーマンスに影響を与えるかを理解する上で不可欠です。

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

このコミットは、Go言語の標準ライブラリにsync.Pool型を導入するものです。sync.Poolは、一時的なオブジェクトの再利用を目的としたプール機構を提供し、ガベージコレクション(GC)の負荷を軽減することで、アプリケーションのパフォーマンス向上に寄与します。特に、頻繁に生成・破棄されるが、そのライフサイクルが短いオブジェクト(例: バイトスライス、バッファなど)の管理に有効です。

コミット

commit 8c6ef061e3c189ac3ac90a451d5680aab9d142618
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Dec 18 11:08:34 2013 -0800

    sync: add Pool type
    
    Adds the Pool type and docs, and use it in fmt.
    This is a temporary implementation, until Dmitry
    makes it fast.
    
    Uses the API proposal from Russ in http://goo.gl/cCKeb2 but
    adds an optional New field, as used in fmt and elsewhere.
    Almost all callers want that.
    
    Update #4720
    
    R=golang-dev, rsc, cshapiro, iant, r, dvyukov, khr
    CC=golang-dev
    https://golang.org/cl/41860043

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

https://github.com/golang/go/commit/8c6ef061e3c189ac3ac90a451d5680aab9d142618

元コミット内容

このコミットは、syncパッケージにPool型を追加し、そのドキュメントを整備しています。また、fmtパッケージでのPoolの使用例も含まれています。コミットメッセージには、これが一時的な実装であり、Dmitry Vyukovによる高速化が予定されていることが明記されています。

APIの設計は、Russ Coxによる提案(http://goo.gl/cCKeb2)に基づいていますが、fmtパッケージなどで必要とされるオプションのNewフィールドが追加されています。これは、ほとんどの利用者が、プールが空の場合に新しい値を生成する機能が必要であるためです。

この変更は、Go issue #4720に関連しています。

変更の背景

Go言語のガベージコレクションは非常に効率的ですが、短命なオブジェクトが大量に生成されるようなシナリオでは、GCのオーバーヘッドが無視できない問題となることがあります。特に、ネットワークプログラミングやI/O処理において、バッファや構造体などのオブジェクトが頻繁にアロケート・デアロケートされる場合、GCが頻繁に実行され、アプリケーションのレイテンシやスループットに影響を与える可能性があります。

sync.Poolの導入は、このような短命なオブジェクトの再利用を促進し、GCの負担を軽減することを目的としています。オブジェクトをプールに格納し、必要に応じて取り出すことで、オブジェクトの新規アロケーションを減らし、GCの実行回数や時間を削減することができます。これにより、アプリケーションのパフォーマンスが向上し、より安定した動作が期待されます。

コミットメッセージにある「This is a temporary implementation, until Dmitry makes it fast.」という記述は、この時点でのsync.Poolの実装が、機能的な側面を優先し、パフォーマンス最適化は後続の作業に委ねられていることを示唆しています。これは、Go言語の開発プロセスにおいて、まず基本的な機能を提供し、その後にパフォーマンスの改善を行うというアプローチが取られることがある典型的な例です。

また、「Uses the API proposal from Russ in http://goo.gl/cCKeb2 but adds an optional New field」という記述は、API設計の経緯を示しています。Russ Coxによる初期の提案をベースにしつつも、実際の利用シナリオ(特にfmtパッケージなど)を考慮して、プールが空の場合に新しいオブジェクトを生成するためのNewフィールドが追加されたことがわかります。これは、APIが単なる概念的な提案だけでなく、実際のコードベースでの利用を通じて洗練されていく過程を示しています。

前提知識の解説

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

Go言語は、自動メモリ管理(ガベージコレクション)を採用しています。これにより、開発者は手動でメモリを解放する手間から解放されます。GoのGCは、並行マーク&スイープ方式をベースにしており、アプリケーションの実行と並行して動作することで、STW(Stop The World)時間を最小限に抑えるように設計されています。しかし、大量のオブジェクトが頻繁に生成・破棄される場合、GCが頻繁にトリガーされ、CPUリソースを消費し、アプリケーションのパフォーマンスに影響を与える可能性があります。

オブジェクトプールパターン

オブジェクトプールパターンは、リソースの生成コストが高い場合に、あらかじめオブジェクトを生成しておき、必要に応じて再利用するデザインパターンです。これにより、オブジェクトの生成・破棄にかかるオーバーヘッドを削減し、パフォーマンスを向上させることができます。sync.Poolは、このオブジェクトプールパターンをGo言語の並行処理環境に適した形で提供するものです。

interface{}

Go言語のinterface{}型は、任意の型の値を保持できる空のインターフェースです。sync.Poolは、このinterface{}型を使用して、様々な型のオブジェクトをプールに格納できるように設計されています。これにより、ジェネリックなオブジェクトプールとして機能します。

sync.Mutex

sync.Mutexは、Go言語における相互排他ロック(ミューテックス)を提供する型です。複数のGoroutineが共有リソースに同時にアクセスする際に、データ競合を防ぐために使用されます。sync.Poolの内部では、複数のGoroutineからのPutGet操作が安全に行われるように、sync.Mutexが使用されています。

runtime.SetFinalizer

runtime.SetFinalizerは、Go言語のランタイム関数で、オブジェクトがガベージコレクタによって回収される直前に実行される関数(ファイナライザ)を設定するために使用されます。sync.Poolのテストコードでは、プールされたオブジェクトがGCによって適切に回収されることを検証するために、このファイナライザが利用されています。

atomicパッケージ

sync/atomicパッケージは、アトミック操作(不可分操作)を提供します。これにより、複数のGoroutineから共有変数にアクセスする際に、ロックを使用せずに安全に値を操作することができます。sync.Poolのテストコードでは、atomic.AddUint32などが使用されており、並行処理環境での正確なカウンタの実装に役立っています。

技術的詳細

sync.Poolは、一時的なオブジェクトを効率的に再利用するためのメカニズムを提供します。その主要な特徴と実装の詳細は以下の通りです。

  1. GCによるクリア: sync.Poolに格納されたオブジェクトは、GCが実行されるたびに自動的にクリアされます。これは、src/pkg/runtime/mgc0.cclearpools関数とruntime·gc関数によって実現されています。GCが開始されると、clearpoolsが呼び出され、登録されているすべてのプールからオブジェクトが削除されます。これにより、プールされたオブジェクトがGCの対象となり、メモリが解放される可能性があります。この挙動は、sync.Poolが「一時的な」オブジェクトのプールであることを強調しています。つまり、プールされたオブジェクトは永続的に保持されることを保証されず、GCによっていつでも破棄される可能性があります。

  2. Putメソッド: Putメソッドは、オブジェクトをプールに追加します。

    • p.mu.Lock()p.mu.Unlock()によって、複数のGoroutineからの同時アクセスが保護されています。
    • p.listという[]interface{}スライスにオブジェクトが追加されます。これは、プールされたオブジェクトを保持するための内部的なストレージです。
    • runtime_registerPool(p)が呼び出され、プールがGoランタイムに登録されます。これにより、GC時にプールがクリアされる対象となります。この登録は、プールが初めて使用される際(p.listnilの場合)に一度だけ行われます。
  3. Getメソッド: Getメソッドは、プールからオブジェクトを取得します。

    • p.mu.Lock()p.mu.Unlock()によって、複数のGoroutineからの同時アクセスが保護されています。
    • p.listスライスの末尾からオブジェクトを取り出します。これは、LIFO(Last-In, First-Out)のような挙動を示します。
    • 取り出した要素はnilに設定され、スライスは短縮されます。これは、参照を確実に解放し、GCがオブジェクトを回収できるようにするためです。
    • プールが空の場合(x == nil)、かつp.Newフィールドが設定されている場合、p.New()関数が呼び出され、新しいオブジェクトが生成されて返されます。これにより、プールが空であっても常に有効なオブジェクトを取得できる保証が提供されます。
  4. Newフィールド: Pool構造体には、オプションのNewフィールドがあります。これは、プールが空のときにGetメソッドが呼び出された場合に、新しいオブジェクトを生成するための関数を指定します。この機能は、プールが空の場合にnilを返すのではなく、常に有効なオブジェクトを提供したい場合に非常に便利です。

  5. ランタイムとの連携: sync.Poolは、Goランタイムのガベージコレクタと密接に連携しています。src/pkg/runtime/mgc0.cの変更は、GCが実行されるたびに登録されたすべてのプールをクリアするメカニズムを導入しています。

    • poolsというグローバルな構造体が導入され、すべてのsync.Poolインスタンスがリンクリストとして管理されます。
    • sync·runtime_registerPool関数は、新しいsync.Poolインスタンスをこのリンクリストに登録します。
    • clearpools関数は、GCの開始時にこのリンクリストを走査し、各プールの内部スライス(p[1]p[2]p[3]はそれぞれlistスライス、muミューテックス、Newフィールドに対応すると考えられる)をnilに設定することで、プールされたオブジェクトへの参照を解除します。これにより、オブジェクトがGCの対象となります。

このGCによるクリアの挙動は、sync.Poolがキャッシュとしてではなく、一時的なオブジェクトの再利用メカニズムとして設計されていることを示しています。つまり、プールされたオブジェクトは、GCサイクルをまたいで永続的に保持されることを期待すべきではありません。

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

このコミットにおける主要なコード変更は以下の3つのファイルにわたります。

  1. src/pkg/runtime/mgc0.c:

    • poolsというグローバルな構造体(Lockheadを持つ)が追加されました。これは、Goランタイムが管理するすべてのsync.Poolインスタンスのリンクリストのヘッドを保持します。
    • sync·runtime_registerPool関数が追加されました。この関数は、Goのsync.Poolから呼び出され、新しいプールインスタンスをランタイムのpoolsリンクリストに登録します。
    • clearpools関数が追加されました。この関数は、poolsリンクリストを走査し、各プールの内部状態(特にlistスライス)をクリアすることで、プールされたオブジェクトへの参照を解除します。
    • runtime·gc関数内で、GCの開始時にclearpools()が呼び出されるように変更されました。これにより、GCが実行されるたびにすべてのsync.Poolがクリアされるようになります。
  2. src/pkg/sync/pool.go:

    • Pool構造体が新しく定義されました。この構造体は、以下のフィールドを持ちます。
      • next *Pool: ランタイムがプールをリンクリストで管理するために使用するフィールド(Goランタイムからのみアクセスされる)。
      • list []interface{}: プールされたオブジェクトを保持するスライス。
      • mu Mutex: listスライスへのアクセスを保護するためのミューテックス。
      • New func() interface{}: プールが空の場合に新しいオブジェクトを生成するためのオプションの関数。
    • runtime_registerPool(*Pool)という外部関数宣言が追加されました。これは、Goランタイムのsync·runtime_registerPool関数をGoコードから呼び出すためのものです。
    • Put(x interface{})メソッドが追加されました。これは、オブジェクトをプールに追加するロジックを実装しています。
    • Get() interface{}メソッドが追加されました。これは、プールからオブジェクトを取得するロジックを実装しており、必要に応じてNew関数を呼び出します。
  3. src/pkg/sync/pool_test.go:

    • TestPool関数: PutGetの基本的な動作、およびGC後のプールのクリア挙動をテストします。
    • TestPoolNew関数: Newフィールドが正しく機能し、プールが空の場合に新しいオブジェクトを生成することを確認します。
    • TestPoolGC関数: runtime.SetFinalizerを使用して、プールから取り出されたオブジェクトがGCによって適切に回収されることを検証します。これは、sync.Poolがオブジェクトへの強い参照を保持しないことを確認するための重要なテストです。
    • TestPoolStress関数: 複数のGoroutineが同時にPutGetを呼び出すストレスシナリオをテストし、並行処理環境でのsync.Poolの堅牢性を確認します。
    • BenchmarkPool関数: sync.Poolのパフォーマンスを測定するためのベンチマークテストです。

コアとなるコードの解説

src/pkg/runtime/mgc0.cの変更

このファイルはGoランタイムのガベージコレクタの一部です。sync.PoolがGCと連携するための重要な変更が含まれています。

static struct
{
	Lock;  
	void* head;
} pools;

void
sync·runtime_registerPool(void **p)
{
	runtime·lock(&pools);
	p[0] = pools.head;
	pools.head = p;
	runtime·unlock(&pools);
}

static void
clearpools(void)
{
	void **p, **next;

	for(p = pools.head; p != nil; p = next) {
		next = p[0];
		p[0] = nil; // next
		p[1] = nil; // slice
		p[2] = nil;
		p[3] = nil;
	}
	pools.head = nil;
}

// ... (runtime·gc関数内)
	clearpools();
  • pools構造体は、Goランタイムが管理するすべてのsync.Poolインスタンスのリンクリストのヘッドを保持します。Lockは、このリンクリストへのアクセスを保護するためのものです。
  • sync·runtime_registerPoolは、Goのsync.Poolから呼び出され、引数p(これはsync.Pool構造体へのポインタ)をpoolsリンクリストの先頭に追加します。p[0]sync.Pool構造体の最初のフィールドであるnext *Poolに対応します。
  • clearpools関数は、GCが開始されるたびに呼び出されます。この関数はpoolsリンクリストを走査し、各プールの内部状態をクリアします。具体的には、p[0]nextポインタ)、p[1]listスライス)、p[2]muミューテックス)、p[3]Newフィールド)をnilに設定します。これにより、プールされたオブジェクトへの参照が解除され、GCの対象となります。
  • runtime·gc関数内でclearpools()が呼び出されることで、GCサイクルごとにプールがリセットされることが保証されます。

src/pkg/sync/pool.goの変更

このファイルはsync.Pool型の定義と、そのPutおよびGetメソッドの実装を含みます。

type Pool struct {
	next *Pool         // for use by runtime. must be first.
	list []interface{} // offset known to runtime
	mu   Mutex         // guards list

	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	// It may not be changed concurrently with calls to Get.
	New func() interface{}
}

func runtime_registerPool(*Pool)

func (p *Pool) Put(x interface{}) {
	if x == nil {
		return
	}
	p.mu.Lock()
	if p.list == nil {
		runtime_registerPool(p)
	}
	p.list = append(p.list, x)
	p.mu.Unlock()
}

func (p *Pool) Get() interface{} {
	p.mu.Lock()
	var x interface{}
	if n := len(p.list); n > 0 {
		x = p.list[n-1]
		p.list[n-1] = nil // Just to be safe
		p.list = p.list[:n-1]
	}
	p.mu.Unlock()
	if x == nil && p.New != nil {
		x = p.New()
	}
	return x
}
  • Pool構造体のnextフィールドは、Goランタイムが内部的にプールをリンクリストで管理するために使用されます。このフィールドが構造体の最初のフィールドであることは、C言語側のp[0]アクセスと整合性を保つために重要です。
  • listフィールドは、interface{}型のスライスであり、プールされたオブジェクトを実際に保持します。
  • muフィールドは、listスライスへのアクセスを保護するためのミューテックスです。
  • Newフィールドは、プールが空の場合に新しいオブジェクトを生成するための関数をオプションで指定できます。
  • Putメソッドでは、オブジェクトをlistスライスに追加する前に、p.list == nilの場合にruntime_registerPool(p)を呼び出して、このプールをランタイムに登録します。これにより、GC時にこのプールがクリアされる対象となります。
  • Getメソッドでは、listスライスの末尾からオブジェクトを取得します。取得後、そのスロットをnilに設定することで、参照を解除し、GCがオブジェクトを回収できるようにします。プールが空で、かつNew関数が設定されている場合は、New関数を呼び出して新しいオブジェクトを生成します。

src/pkg/sync/pool_test.goの変更

このファイルは、sync.Poolの機能と挙動を検証するためのテストケースを含みます。特に注目すべきは、GCとの連携をテストする部分です。

func TestPool(t *testing.T) {
	// disable GC so we can control when it happens.
	defer debug.SetGCPercent(debug.SetGCPercent(-1))
	var p Pool
	// ...
	p.Put("c")
	debug.SetGCPercent(100) // to allow following GC to actually run
	runtime.GC()
	if g := p.Get(); g != nil {
		t.Fatalf("got %#v; want nil after GC", g)
	}
}

func TestPoolGC(t *testing.T) {
	var p Pool
	var fin uint32
	const N = 100
	for i := 0; i < N; i++ {
		v := new(int)
		runtime.SetFinalizer(v, func(vv *int) {
			atomic.AddUint32(&fin, 1)
		})
		p.Put(v)
	}
	for i := 0; i < N; i++ {
		p.Get()
	}
	for i := 0; i < 5; i++ {
		runtime.GC()
		time.Sleep(time.Millisecond)
		// 1 pointer can remain on stack or elsewhere
		if atomic.LoadUint32(&fin) >= N-1 {
			return
		}
	}
	t.Fatalf("only %v out of %v resources are finalized",
		atomic.LoadUint32(&fin), N)
}
  • TestPoolでは、debug.SetGCPercent(-1)でGCを一時的に無効にし、runtime.GC()を明示的に呼び出すことで、GCがsync.Poolに与える影響を制御してテストしています。GC後にプールがクリアされ、Getnilを返すことを確認しています。
  • TestPoolGCでは、runtime.SetFinalizerを使用して、プールされたオブジェクトがGCによって実際に回収されることを検証しています。これは、sync.Poolがオブジェクトへの強い参照を保持せず、GCの対象となることを保証するための重要なテストです。N個のオブジェクトをプールに入れ、すべて取り出した後、GCを複数回実行し、ファイナライザが呼び出された回数をatomic.AddUint32でカウントしています。これにより、ほとんどすべてのオブジェクトがファイナライズされることを確認しています。

これらのコード変更は、sync.PoolがGoランタイムのGCと連携し、一時的なオブジェクトの再利用を効率的に行うための基盤を確立していることを示しています。

関連リンク

  • Go issue #4720: sync.Poolの導入に関連する議論のトラッキングイシュー。
  • Russ CoxによるAPI提案: https://go.dev/issue/4720 (元のgoo.glリンクはGo issue #4720にリダイレクトされます)
  • Go CL 41860043: https://go-review.googlesource.com/c/go/+/41860043 このコミットに対応するGoのコードレビューシステム(Gerrit)上のチェンジリスト。

参考にした情報源リンク

  • Go言語の公式ドキュメント: sync.Poolに関する最新の情報や使用例は、Go言語の公式ドキュメントを参照するのが最も正確です。
  • Go言語のソースコード: src/sync/pool.gosrc/runtime/mgc.go(または関連するGCファイル)のソースコードは、sync.Poolの内部動作を深く理解するための究極の情報源です。
  • Go言語のブログやカンファレンス発表: sync.Poolの設計思想やパフォーマンスに関する情報は、Go言語の公式ブログやGopherConなどのカンファレンス発表で解説されていることがあります。
  • Go issue #4720の議論: このイシューのコメントスレッドには、sync.Poolの設計に関する詳細な議論や、初期の懸念事項、解決策などが含まれている可能性があります。
  • Go言語のガベージコレクションに関する資料: GoのGCの仕組みを理解することは、sync.PoolがGCとどのように連携し、パフォーマンスに影響を与えるかを理解する上で不可欠です。
  • Go issue #23199: sync.Poolの誤った使用法とメモリ効率に関する議論。sync.Poolの適切な利用シナリオを理解する上で重要です。