[インデックス 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からのPut
やGet
操作が安全に行われるように、sync.Mutex
が使用されています。
runtime.SetFinalizer
runtime.SetFinalizer
は、Go言語のランタイム関数で、オブジェクトがガベージコレクタによって回収される直前に実行される関数(ファイナライザ)を設定するために使用されます。sync.Pool
のテストコードでは、プールされたオブジェクトがGCによって適切に回収されることを検証するために、このファイナライザが利用されています。
atomic
パッケージ
sync/atomic
パッケージは、アトミック操作(不可分操作)を提供します。これにより、複数のGoroutineから共有変数にアクセスする際に、ロックを使用せずに安全に値を操作することができます。sync.Pool
のテストコードでは、atomic.AddUint32
などが使用されており、並行処理環境での正確なカウンタの実装に役立っています。
技術的詳細
sync.Pool
は、一時的なオブジェクトを効率的に再利用するためのメカニズムを提供します。その主要な特徴と実装の詳細は以下の通りです。
-
GCによるクリア:
sync.Pool
に格納されたオブジェクトは、GCが実行されるたびに自動的にクリアされます。これは、src/pkg/runtime/mgc0.c
のclearpools
関数とruntime·gc
関数によって実現されています。GCが開始されると、clearpools
が呼び出され、登録されているすべてのプールからオブジェクトが削除されます。これにより、プールされたオブジェクトがGCの対象となり、メモリが解放される可能性があります。この挙動は、sync.Pool
が「一時的な」オブジェクトのプールであることを強調しています。つまり、プールされたオブジェクトは永続的に保持されることを保証されず、GCによっていつでも破棄される可能性があります。 -
Put
メソッド:Put
メソッドは、オブジェクトをプールに追加します。p.mu.Lock()
とp.mu.Unlock()
によって、複数のGoroutineからの同時アクセスが保護されています。p.list
という[]interface{}
スライスにオブジェクトが追加されます。これは、プールされたオブジェクトを保持するための内部的なストレージです。runtime_registerPool(p)
が呼び出され、プールがGoランタイムに登録されます。これにより、GC時にプールがクリアされる対象となります。この登録は、プールが初めて使用される際(p.list
がnil
の場合)に一度だけ行われます。
-
Get
メソッド:Get
メソッドは、プールからオブジェクトを取得します。p.mu.Lock()
とp.mu.Unlock()
によって、複数のGoroutineからの同時アクセスが保護されています。p.list
スライスの末尾からオブジェクトを取り出します。これは、LIFO(Last-In, First-Out)のような挙動を示します。- 取り出した要素は
nil
に設定され、スライスは短縮されます。これは、参照を確実に解放し、GCがオブジェクトを回収できるようにするためです。 - プールが空の場合(
x == nil
)、かつp.New
フィールドが設定されている場合、p.New()
関数が呼び出され、新しいオブジェクトが生成されて返されます。これにより、プールが空であっても常に有効なオブジェクトを取得できる保証が提供されます。
-
New
フィールド:Pool
構造体には、オプションのNew
フィールドがあります。これは、プールが空のときにGet
メソッドが呼び出された場合に、新しいオブジェクトを生成するための関数を指定します。この機能は、プールが空の場合にnil
を返すのではなく、常に有効なオブジェクトを提供したい場合に非常に便利です。 -
ランタイムとの連携:
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つのファイルにわたります。
-
src/pkg/runtime/mgc0.c
:pools
というグローバルな構造体(Lock
とhead
を持つ)が追加されました。これは、Goランタイムが管理するすべてのsync.Pool
インスタンスのリンクリストのヘッドを保持します。sync·runtime_registerPool
関数が追加されました。この関数は、Goのsync.Pool
から呼び出され、新しいプールインスタンスをランタイムのpools
リンクリストに登録します。clearpools
関数が追加されました。この関数は、pools
リンクリストを走査し、各プールの内部状態(特にlist
スライス)をクリアすることで、プールされたオブジェクトへの参照を解除します。runtime·gc
関数内で、GCの開始時にclearpools()
が呼び出されるように変更されました。これにより、GCが実行されるたびにすべてのsync.Pool
がクリアされるようになります。
-
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
関数を呼び出します。
-
src/pkg/sync/pool_test.go
:TestPool
関数:Put
とGet
の基本的な動作、およびGC後のプールのクリア挙動をテストします。TestPoolNew
関数:New
フィールドが正しく機能し、プールが空の場合に新しいオブジェクトを生成することを確認します。TestPoolGC
関数:runtime.SetFinalizer
を使用して、プールから取り出されたオブジェクトがGCによって適切に回収されることを検証します。これは、sync.Pool
がオブジェクトへの強い参照を保持しないことを確認するための重要なテストです。TestPoolStress
関数: 複数のGoroutineが同時にPut
とGet
を呼び出すストレスシナリオをテストし、並行処理環境での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後にプールがクリアされ、Get
がnil
を返すことを確認しています。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.go
、src/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からのPut
やGet
操作が安全に行われるように、sync.Mutex
が使用されています。
runtime.SetFinalizer
runtime.SetFinalizer
は、Go言語のランタイム関数で、オブジェクトがガベージコレクタによって回収される直前に実行される関数(ファイナライザ)を設定するために使用されます。sync.Pool
のテストコードでは、プールされたオブジェクトがGCによって適切に回収されることを検証するために、このファイナライザが利用されています。
atomic
パッケージ
sync/atomic
パッケージは、アトミック操作(不可分操作)を提供します。これにより、複数のGoroutineから共有変数にアクセスする際に、ロックを使用せずに安全に値を操作することができます。sync.Pool
のテストコードでは、atomic.AddUint32
などが使用されており、並行処理環境での正確なカウンタの実装に役立っています。
技術的詳細
sync.Pool
は、一時的なオブジェクトを効率的に再利用するためのメカニズムを提供します。その主要な特徴と実装の詳細は以下の通りです。
-
GCによるクリア:
sync.Pool
に格納されたオブジェクトは、GCが実行されるたびに自動的にクリアされます。これは、src/pkg/runtime/mgc0.c
のclearpools
関数とruntime·gc
関数によって実現されています。GCが開始されると、clearpools
が呼び出され、登録されているすべてのプールからオブジェクトが削除されます。これにより、プールされたオブジェクトがGCの対象となり、メモリが解放される可能性があります。この挙動は、sync.Pool
が「一時的な」オブジェクトのプールであることを強調しています。つまり、プールされたオブジェクトは永続的に保持されることを保証されず、GCによっていつでも破棄される可能性があります。 -
Put
メソッド:Put
メソッドは、オブジェクトをプールに追加します。p.mu.Lock()
とp.mu.Unlock()
によって、複数のGoroutineからの同時アクセスが保護されています。p.list
という[]interface{}
スライスにオブジェクトが追加されます。これは、プールされたオブジェクトを保持するための内部的なストレージです。runtime_registerPool(p)
が呼び出され、プールがGoランタイムに登録されます。これにより、GC時にプールがクリアされる対象となります。この登録は、プールが初めて使用される際(p.list
がnil
の場合)に一度だけ行われます。
-
Get
メソッド:Get
メソッドは、プールからオブジェクトを取得します。p.mu.Lock()
とp.mu.Unlock()
によって、複数のGoroutineからの同時アクセスが保護されています。p.list
スライスの末尾からオブジェクトを取り出します。これは、LIFO(Last-In, First-Out)のような挙動を示します。- 取り出した要素は
nil
に設定され、スライスは短縮されます。これは、参照を確実に解放し、GCがオブジェクトを回収できるようにするためです。 - プールが空の場合(
x == nil
)、かつp.New
フィールドが設定されている場合、p.New()
関数が呼び出され、新しいオブジェクトが生成されて返されます。これにより、プールが空であっても常に有効なオブジェクトを取得できる保証が提供されます。
-
New
フィールド:Pool
構造体には、オプションのNew
フィールドがあります。これは、プールが空のときにGet
メソッドが呼び出された場合に、新しいオブジェクトを生成するための関数を指定します。この機能は、プールが空の場合にnil
を返すのではなく、常に有効なオブジェクトを提供したい場合に非常に便利です。 -
ランタイムとの連携:
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つのファイルにわたります。
-
src/pkg/runtime/mgc0.c
:pools
というグローバルな構造体(Lock
とhead
を持つ)が追加されました。これは、Goランタイムが管理するすべてのsync.Pool
インスタンスのリンクリストのヘッドを保持します。sync·runtime_registerPool
関数が追加されました。この関数は、Goのsync.Pool
から呼び出され、新しいプールインスタンスをランタイムのpools
リンクリストに登録します。clearpools
関数が追加されました。この関数は、pools
リンクリストを走査し、各プールの内部状態(特にlist
スライス)をクリアすることで、プールされたオブジェクトへの参照を解除します。runtime·gc
関数内で、GCの開始時にclearpools()
が呼び出されるように変更されました。これにより、GCが実行されるたびにすべてのsync.Pool
がクリアされるようになります。
-
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
関数を呼び出します。
-
src/pkg/sync/pool_test.go
:TestPool
関数:Put
とGet
の基本的な動作、およびGC後のプールのクリア挙動をテストします。TestPoolNew
関数:New
フィールドが正しく機能し、プールが空の場合に新しいオブジェクトを生成することを確認します。TestPoolGC
関数:runtime.SetFinalizer
を使用して、プールから取り出されたオブジェクトがGCによって適切に回収されることを検証します。これは、sync.Pool
がオブジェクトへの強い参照を保持しないことを確認するための重要なテストです。TestPoolStress
関数: 複数のGoroutineが同時にPut
とGet
を呼び出すストレスシナリオをテストし、並行処理環境での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後にプールがクリアされ、Get
がnil
を返すことを確認しています。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.go
、src/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
の適切な利用シナリオを理解する上で重要です。