[インデックス 18177] ファイルの概要
このコミットは、Go言語の標準ライブラリであるregexp
パッケージにおける正規表現エンジンの内部的なマシン(machine
)の管理方法に関する変更です。具体的には、以前のコミットで導入されたsync.Pool
の使用を取り消し、カスタムのスライスとsync.Mutex
を用いたキャッシュ機構に戻すものです。
コミット
commit 90e9669c50fc471e14b358382a60b6354182fb2d
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Jan 6 12:38:04 2014 -0800
undo CL 44150043 / 198bdc0984dd
See https://golang.org/cl/44150043/
««« original CL description
regexp: use sync.Pool
For machines, not threads.
Update #4720
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/44150043
»»»
TBR=golang-dev
CC=golang-codereviews
https://golang.org/cl/48190043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/90e9669c50fc471e14b358382a60b6354182fb2d
元コミット内容
このコミットは、以下の元のコミット(CL 44150043 / 198bdc0984dd)の変更を取り消すものです。
regexp: use sync.Pool
For machines, not threads.
Update #4720
元のコミットは、regexp
パッケージが正規表現のマッチングに使用する内部的な「マシン」(machine
構造体)の再利用のためにsync.Pool
を導入しようとしました。これは、machine
オブジェクトの頻繁な生成とガベージコレクションのオーバーヘッドを削減することを目的としていました。
変更の背景
このコミットの背景には、sync.Pool
の設計とregexp
パッケージにおけるmachine
オブジェクトのライフサイクルとのミスマッチがあったと考えられます。
sync.Pool
は、一時的なオブジェクトの再利用を目的としたプールであり、ガベージコレクション(GC)のサイクルでプール内のオブジェクトがクリアされる可能性があります。これは、プールされたオブジェクトがGCによって回収されることで、プールの効果が薄れる、あるいは予期せぬパフォーマンス特性を示す可能性があることを意味します。
regexp
パッケージのmachine
オブジェクトは、正規表現のマッチング処理中に一時的に使用されますが、その再利用はパフォーマンスに大きく影響します。もしsync.Pool
がmachine
オブジェクトを頻繁にクリアしてしまうと、期待されるパフォーマンス改善が得られないか、むしろ悪化する可能性がありました。
元のコミット(CL 44150043)は、regexp
のマッチング処理におけるアロケーションを減らすためにsync.Pool
を導入しましたが、その後の評価で、このアプローチが期待通りの効果を発揮しない、あるいは問題を引き起こすことが判明したため、このコミット(CL 48190043)でその変更が取り消されることになりました。
前提知識の解説
正規表現エンジンにおける「マシン」
正規表現のマッチングは、内部的に有限オートマトン(DFAやNFA)のような状態機械によって行われます。Goのregexp
パッケージも同様に、正規表現のパターンを解析して内部的な「プログラム」(prog
)を生成し、そのプログラムを実行するための「マシン」(machine
構造体)を使用します。このmachine
オブジェクトは、マッチングの現在の状態、入力文字列の現在位置、キャプチャグループの情報などを保持します。
正規表現のマッチングが頻繁に行われるアプリケーションでは、このmachine
オブジェクトが繰り返し生成・破棄されることになり、ガベージコレクションの負荷が増大し、パフォーマンスのボトルネックとなる可能性があります。そのため、これらのオブジェクトを再利用するメカニズムが重要になります。
sync.Pool
sync.Pool
はGo言語の標準ライブラリsync
パッケージで提供される型で、一時的なオブジェクトの再利用を目的とした同期プールです。主な特徴は以下の通りです。
- 一時的なオブジェクトの再利用:
sync.Pool
は、頻繁に生成・破棄される一時的なオブジェクトをプールし、再利用することで、ガベージコレクションの負荷を軽減し、パフォーマンスを向上させることを目的としています。 - GCによるクリア:
sync.Pool
に格納されたオブジェクトは、ガベージコレクションのサイクル中にいつでもクリアされる可能性があります。これは、プールが「キャッシュ」ではなく「一時的なオブジェクトの再利用メカニズム」として設計されているためです。プールされたオブジェクトがGCによって回収されても、プログラムの正当性には影響しませんが、プールの効果が一時的に失われる可能性があります。 Get()
とPut()
:Get()
メソッドはプールからオブジェクトを取得します。プールが空の場合、New
フィールドに設定された関数が呼び出されて新しいオブジェクトが生成されます。Put()
メソッドはオブジェクトをプールに戻します。
- スレッドセーフ:
sync.Pool
は内部的に同期されており、複数のGoroutineから安全にアクセスできます。
sync.Pool
は、例えばHTTPリクエストの処理中に一時的に使用されるバッファや、JSONエンコーダ/デコーダのようなオブジェクトの再利用に適しています。しかし、GCによってクリアされる特性から、常にオブジェクトがプールに存在することを期待するような用途(例えば、コネクションプールのように、常に一定数のリソースを確保したい場合)には適していません。
sync.Mutex
sync.Mutex
はGo言語の標準ライブラリsync
パッケージで提供される相互排他ロックです。複数のGoroutineが共有リソースに同時にアクセスするのを防ぎ、データ競合を防ぐために使用されます。
Lock()
とUnlock()
:Lock()
メソッドはミューテックスをロックします。既にロックされている場合、呼び出し元のGoroutineはロックが解放されるまでブロックされます。Unlock()
メソッドはミューテックスをアンロックします。
- 共有リソースの保護:
sync.Mutex
は、共有データ構造(例えば、スライスやマップ)へのアクセスを保護するために使用され、一度に一つのGoroutineだけがそのデータにアクセスできるようにします。
このコミットでは、sync.Pool
の代わりに、カスタムのスライスとsync.Mutex
を組み合わせてmachine
オブジェクトのキャッシュを実装しています。これは、sync.Pool
のGCによるクリアの特性を避け、より制御された方法でmachine
オブジェクトを管理するためと考えられます。
技術的詳細
このコミットは、regexp
パッケージのRegexp
構造体からsync.Pool
フィールドを削除し、代わりにカスタムの[]*machine
スライスとsync.Mutex
を導入することで、正規表現のマッチングに使用されるmachine
オブジェクトのキャッシュメカニズムを変更します。
変更点
-
Regexp
構造体の変更:machinePool sync.Pool // of *machine
フィールドが削除されました。- 代わりに、
mu sync.Mutex
とmachine []*machine
フィールドが追加されました。mu
は、machine
スライスへのアクセスを同期するためのミューテックスです。machine
は、再利用可能な*machine
オブジェクトを保持するスライスです。
-
get()
メソッドの変更:- 以前は
re.machinePool.Get()
を呼び出してプールからmachine
オブジェクトを取得していました。 - 変更後、
re.mu.Lock()
でロックを取得し、re.machine
スライスから最後の要素を取り出して返します。スライスが空の場合は、新しいmachine
オブジェクトを生成します。最後にre.mu.Unlock()
でロックを解放します。 - このカスタム実装により、
sync.Pool
のGCによるクリアの影響を受けずに、machine
オブジェクトを明示的に管理できます。
- 以前は
-
put()
メソッドの変更:- 以前は
re.machinePool.Put(z)
を呼び出してmachine
オブジェクトをプールに戻していました。 - 変更後、
re.mu.Lock()
でロックを取得し、re.machine
スライスにmachine
オブジェクトを追加します。最後にre.mu.Unlock()
でロックを解放します。
- 以前は
なぜsync.Pool
からカスタムキャッシュへ?
この変更の主な理由は、sync.Pool
の特性、特にガベージコレクション(GC)のサイクルでプールされたオブジェクトがクリアされる可能性がある点にあります。
- GCによるクリアの問題:
sync.Pool
は、GCが実行されるたびにプール内のオブジェクトをクリアする可能性があります。これは、プールが「キャッシュ」ではなく「一時的なオブジェクトの再利用メカニズム」として設計されているためです。regexp
のmachine
オブジェクトは、マッチングのたびに頻繁に取得・解放されるため、もしプールが頻繁にクリアされると、オブジェクトの再利用率が低下し、アロケーションとGCのオーバーヘッドが期待通りに削減されない可能性があります。 - 制御の必要性:
regexp
パッケージは、パフォーマンスが非常に重要なコンポーネントです。machine
オブジェクトの再利用は、そのパフォーマンスに直接影響します。sync.Pool
の自動的なクリアメカニズムでは、machine
オブジェクトのライフサイクルを完全に制御することが難しく、特定のワークロードで予期せぬパフォーマンス特性を示す可能性がありました。 - カスタムキャッシュの利点: カスタムのスライスとミューテックスを用いたキャッシュは、GCの影響を受けずに、
machine
オブジェクトを明示的に保持し続けることができます。これにより、より安定した再利用率と予測可能なパフォーマンスが期待できます。Regexp
オブジェクト自体がガベージコレクションされるまで、その内部のmachine
スライスはオブジェクトを保持し続けます。
この変更は、regexp
パッケージのパフォーマンスと安定性を確保するために、より堅牢で予測可能なキャッシュメカニズムを選択した結果と言えます。
コアとなるコードの変更箇所
src/pkg/regexp/regexp.go
ファイルが変更されています。
--- a/src/pkg/regexp/regexp.go
+++ b/src/pkg/regexp/regexp.go
@@ -85,8 +85,9 @@ type Regexp struct {
subexpNames []string
longest bool
-// pool of machines for running regexp
- machinePool sync.Pool // of *machine
+// cache of machines for running regexp
+ mu sync.Mutex
+ machine []*machine
}
// String returns the source text used to compile the regular expression.
@@ -174,9 +175,14 @@ func compile(expr string, mode syntax.Flags, longest bool) (*Regexp, error) {
// It uses the re's machine cache if possible, to avoid
// unnecessary allocation.
func (re *Regexp) get() *machine {
- if v := re.machinePool.Get(); v != nil {
- return v.(*machine)
- }
+ re.mu.Lock()
+ if n := len(re.machine); n > 0 {
+ z := re.machine[n-1]
+ re.machine = re.machine[:n-1]
+ re.mu.Unlock()
+ return z
+ }
+ re.mu.Unlock()
z := progMachine(re.prog)
z.re = re
return z
@@ -187,7 +193,9 @@ func (re *Regexp) put(z *machine) {
// grow to the maximum number of simultaneous matches
// run using re. (The cache empties when re gets garbage collected.)
func (re *Regexp) put(z *machine) {
- re.machinePool.Put(z)
+ re.mu.Lock()
+ re.machine = append(re.machine, z)
+ re.mu.Unlock()
}
// MustCompile is like Compile but panics if the expression cannot be parsed.
コアとなるコードの解説
Regexp
構造体
type Regexp struct {
subexpNames []string
longest bool
// cache of machines for running regexp
mu sync.Mutex
machine []*machine
}
machinePool sync.Pool
が削除され、代わりにmu sync.Mutex
とmachine []*machine
が追加されました。mu
は、machine
スライスへのアクセスを保護するためのミューテックスです。これにより、複数のGoroutineが同時にmachine
スライスにアクセスしても、データ競合が発生しないようにします。machine
は、再利用可能な*machine
オブジェクトを格納するためのスライスです。このスライスが、正規表現のマッチングに使用されるmachine
オブジェクトのカスタムキャッシュとして機能します。
get()
メソッド
func (re *Regexp) get() *machine {
re.mu.Lock() // ロックを取得
if n := len(re.machine); n > 0 { // キャッシュにmachineがあるかチェック
z := re.machine[n-1] // 最後の要素を取得
re.machine = re.machine[:n-1] // スライスから削除
re.mu.Unlock() // ロックを解放
return z // 取得したmachineを返す
}
re.mu.Unlock() // キャッシュが空の場合、ロックを解放
z := progMachine(re.prog) // 新しいmachineを生成
z.re = re
return z
}
- このメソッドは、正規表現のマッチングに使用する
machine
オブジェクトを取得します。 - まず
re.mu.Lock()
を呼び出してミューテックスをロックし、machine
スライスへの排他的アクセスを確保します。 if n := len(re.machine); n > 0
で、machine
スライスに再利用可能なmachine
オブジェクトが存在するかどうかを確認します。- もし存在する場合(
n > 0
)、スライスの最後の要素(re.machine[n-1]
)を取り出し、その要素をスライスから削除します(re.machine = re.machine[:n-1]
)。その後、re.mu.Unlock()
でロックを解放し、取得したmachine
オブジェクトを返します。 - もしスライスが空の場合、
re.mu.Unlock()
でロックを解放し、progMachine(re.prog)
を呼び出して新しいmachine
オブジェクトを生成して返します。
put()
メソッド
func (re *Regexp) put(z *machine) {
re.mu.Lock() // ロックを取得
re.machine = append(re.machine, z) // machineをスライスに追加
re.mu.Unlock() // ロックを解放
}
- このメソッドは、使用済みの
machine
オブジェクトをキャッシュに戻します。 re.mu.Lock()
を呼び出してミューテックスをロックします。re.machine = append(re.machine, z)
で、引数で渡されたmachine
オブジェクトz
をmachine
スライスの末尾に追加します。- 最後に
re.mu.Unlock()
でロックを解放します。
これらの変更により、regexp
パッケージはsync.Pool
の自動的なクリアメカニズムに依存せず、より直接的かつ制御された方法でmachine
オブジェクトの再利用を行うようになりました。これにより、ガベージコレクションのオーバーヘッドをより効果的に削減し、正規表現のマッチングパフォーマンスを安定させることが期待されます。
関連リンク
- Go言語の
sync.Pool
に関する公式ドキュメント: https://pkg.go.dev/sync#Pool - Go言語の
sync.Mutex
に関する公式ドキュメント: https://pkg.go.dev/sync#Mutex - 元のコミット (CL 44150043): https://golang.org/cl/44150043
- このコミット (CL 48190043): https://golang.org/cl/48190043
- 関連するIssue #4720: https://golang.org/issue/4720
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
sync.Pool
の利用に関する一般的な議論やベストプラクティスに関する記事 (例: "When to use sync.Pool" などで検索)- Goの正規表現エンジンの内部実装に関する資料 (例: "Go regexp package internals" などで検索)
- GitHubのgolang/goリポジトリのコミット履歴と関連するIssue。
[インデックス 18177] ファイルの概要
このコミットは、Go言語の標準ライブラリであるregexp
パッケージにおける正規表現エンジンの内部的なマシン(machine
)の管理方法に関する変更です。具体的には、以前のコミットで導入されたsync.Pool
の使用を取り消し、カスタムのスライスとsync.Mutex
を用いたキャッシュ機構に戻すものです。
コミット
commit 90e9669c50fc471e14b358382a60b6354182fb2d
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Jan 6 12:38:04 2014 -0800
undo CL 44150043 / 198bdc0984dd
See https://golang.org/cl/44150043/
««« original CL description
regexp: use sync.Pool
For machines, not threads.
Update #4720
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/44150043
»»»
TBR=golang-dev
CC=golang-codereviews
https://golang.org/cl/48190043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/90e9669c50fc471e14b358382a60b6354182fb2d
元コミット内容
このコミットは、以下の元のコミット(CL 44150043 / 198bdc0984dd)の変更を取り消すものです。
regexp: use sync.Pool
For machines, not threads.
Update #4720
元のコミットは、regexp
パッケージが正規表現のマッチングに使用する内部的な「マシン」(machine
構造体)の再利用のためにsync.Pool
を導入しようとしました。これは、machine
オブジェクトの頻繁な生成とガベージコレクションのオーバーヘッドを削減することを目的としていました。
変更の背景
このコミットの背景には、sync.Pool
の設計とregexp
パッケージにおけるmachine
オブジェクトのライフサイクルとのミスマッチがあったと考えられます。
sync.Pool
は、一時的なオブジェクトの再利用を目的としたプールであり、ガベージコレクション(GC)のサイクルでプール内のオブジェクトがクリアされる可能性があります。これは、プールされたオブジェクトがGCによって回収されることで、プールの効果が薄れる、あるいは予期せぬパフォーマンス特性を示す可能性があることを意味します。
regexp
パッケージのmachine
オブジェクトは、正規表現のマッチング処理中に一時的に使用されますが、その再利用はパフォーマンスに大きく影響します。もしsync.Pool
がmachine
オブジェクトを頻繁にクリアしてしまうと、期待されるパフォーマンス改善が得られないか、むしろ悪化する可能性がありました。
元のコミット(CL 44150043)は、regexp
のマッチング処理におけるアロケーションを減らすためにsync.Pool
を導入しましたが、その後の評価で、このアプローチが期待通りの効果を発揮しない、あるいは問題を引き起こすことが判明したため、このコミット(CL 48190043)でその変更が取り消されることになりました。
前提知識の解説
正規表現エンジンにおける「マシン」
正規表現のマッチングは、内部的に有限オートマトン(DFAやNFA)のような状態機械によって行われます。Goのregexp
パッケージも同様に、正規表現のパターンを解析して内部的な「プログラム」(prog
)を生成し、そのプログラムを実行するための「マシン」(machine
構造体)を使用します。このmachine
オブジェクトは、マッチングの現在の状態、入力文字列の現在位置、キャプチャグループの情報などを保持します。
正規表現のマッチングが頻繁に行われるアプリケーションでは、このmachine
オブジェクトが繰り返し生成・破棄されることになり、ガベージコレクションの負荷が増大し、パフォーマンスのボトルネックとなる可能性があります。そのため、これらのオブジェクトを再利用するメカニズムが重要になります。
sync.Pool
sync.Pool
はGo言語の標準ライブラリsync
パッケージで提供される型で、一時的なオブジェクトの再利用を目的とした同期プールです。主な特徴は以下の通りです。
- 一時的なオブジェクトの再利用:
sync.Pool
は、頻繁に生成・破棄される一時的なオブジェクトをプールし、再利用することで、ガベージコレクションの負荷を軽減し、パフォーマンスを向上させることを目的としています。 - GCによるクリア:
sync.Pool
に格納されたオブジェクトは、ガベージコレクションのサイクル中にいつでもクリアされる可能性があります。これは、プールが「キャッシュ」ではなく「一時的なオブジェクトの再利用メカニズム」として設計されているためです。プールされたオブジェクトがGCによって回収されても、プログラムの正当性には影響しませんが、プールの効果が一時的に失われる可能性があります。Go 1.13以降では「victim cache」が導入され、GCサイクル中にプールが完全に空になることを緩和する改善がなされていますが、それでも一時的な性質は変わりません。 Get()
とPut()
:Get()
メソッドはプールからオブジェクトを取得します。プールが空の場合、New
フィールドに設定された関数が呼び出されて新しいオブジェクトが生成されます。Put()
メソッドはオブジェクトをプールに戻します。
- スレッドセーフ:
sync.Pool
は内部的に同期されており、複数のGoroutineから安全にアクセスできます。
sync.Pool
は、例えばHTTPリクエストの処理中に一時的に使用されるバッファや、JSONエンコーダ/デコーダのようなオブジェクトの再利用に適しています。しかし、GCによってクリアされる特性から、常にオブジェクトがプールに存在することを期待するような用途(例えば、コネクションプールのように、常に一定数のリソースを確保したい場合)には適していません。
sync.Mutex
sync.Mutex
はGo言語の標準ライブラリsync
パッケージで提供される相互排他ロックです。複数のGoroutineが共有リソースに同時にアクセスするのを防ぎ、データ競合を防ぐために使用されます。
Lock()
とUnlock()
:Lock()
メソッドはミューテックスをロックします。既にロックされている場合、呼び出し元のGoroutineはロックが解放されるまでブロックされます。Unlock()
メソッドはミューテックスをアンロックします。
- 共有リソースの保護:
sync.Mutex
は、共有データ構造(例えば、スライスやマップ)へのアクセスを保護するために使用され、一度に一つのGoroutineだけがそのデータにアクセスできるようにします。
このコミットでは、sync.Pool
の代わりに、カスタムのスライスとsync.Mutex
を組み合わせてmachine
オブジェクトのキャッシュを実装しています。これは、sync.Pool
のGCによるクリアの特性を避け、より制御された方法でmachine
オブジェクトを管理するためと考えられます。
技術的詳細
このコミットは、regexp
パッケージのRegexp
構造体からsync.Pool
フィールドを削除し、代わりにカスタムの[]*machine
スライスとsync.Mutex
を導入することで、正規表現のマッチングに使用されるmachine
オブジェクトのキャッシュメカニズムを変更します。
変更点
-
Regexp
構造体の変更:machinePool sync.Pool // of *machine
フィールドが削除されました。- 代わりに、
mu sync.Mutex
とmachine []*machine
フィールドが追加されました。mu
は、machine
スライスへのアクセスを同期するためのミューテックスです。machine
は、再利用可能な*machine
オブジェクトを保持するスライスです。
-
get()
メソッドの変更:- 以前は
re.machinePool.Get()
を呼び出してプールからmachine
オブジェクトを取得していました。 - 変更後、
re.mu.Lock()
でロックを取得し、re.machine
スライスから最後の要素を取り出して返します。スライスが空の場合は、新しいmachine
オブジェクトを生成します。最後にre.mu.Unlock()
でロックを解放します。 - このカスタム実装により、
sync.Pool
のGCによるクリアの影響を受けずに、machine
オブジェクトを明示的に管理できます。
- 以前は
-
put()
メソッドの変更:- 以前は
re.machinePool.Put(z)
を呼び出してmachine
オブジェクトをプールに戻していました。 - 変更後、
re.mu.Lock()
でロックを取得し、re.machine
スライスにmachine
オブジェクトを追加します。最後にre.mu.Unlock()
でロックを解放します。
- 以前は
なぜsync.Pool
からカスタムキャッシュへ?
この変更の主な理由は、sync.Pool
の特性、特にガベージコレクション(GC)のサイクルでプールされたオブジェクトがクリアされる可能性がある点にあります。
- GCによるクリアの問題:
sync.Pool
は、GCが実行されるたびにプール内のオブジェクトをクリアする可能性があります。これは、プールが「キャッシュ」ではなく「一時的なオブジェクトの再利用メカニズム」として設計されているためです。regexp
のmachine
オブジェクトは、マッチングのたびに頻繁に取得・解放されるため、もしプールが頻繁にクリアされると、オブジェクトの再利用率が低下し、アロケーションとGCのオーバーヘッドが期待通りに削減されない可能性があります。 - 制御の必要性:
regexp
パッケージは、パフォーマンスが非常に重要なコンポーネントです。machine
オブジェクトの再利用は、そのパフォーマンスに直接影響します。sync.Pool
の自動的なクリアメカニズムでは、machine
オブジェクトのライフサイクルを完全に制御することが難しく、特定のワークロードで予期せぬパフォーマンス特性を示す可能性がありました。 - カスタムキャッシュの利点: カスタムのスライスとミューテックスを用いたキャッシュは、GCの影響を受けずに、
machine
オブジェクトを明示的に保持し続けることができます。これにより、より安定した再利用率と予測可能なパフォーマンスが期待できます。Regexp
オブジェクト自体がガベージコレクションされるまで、その内部のmachine
スライスはオブジェクトを保持し続けます。
この変更は、regexp
パッケージのパフォーマンスと安定性を確保するために、より堅牢で予測可能なキャッシュメカニズムを選択した結果と言えます。
コアとなるコードの変更箇所
src/pkg/regexp/regexp.go
ファイルが変更されています。
--- a/src/pkg/regexp/regexp.go
+++ b/src/pkg/regexp/regexp.go
@@ -85,8 +85,9 @@ type Regexp struct {
subexpNames []string
longest bool
-// pool of machines for running regexp
- machinePool sync.Pool // of *machine
+// cache of machines for running regexp
+ mu sync.Mutex
+ machine []*machine
}
// String returns the source text used to compile the regular expression.
@@ -174,9 +175,14 @@ func compile(expr string, mode syntax.Flags, longest bool) (*Regexp, error) {\
// It uses the re's machine cache if possible, to avoid
// unnecessary allocation.
func (re *Regexp) get() *machine {
- if v := re.machinePool.Get(); v != nil {
- return v.(*machine)
- }
+ re.mu.Lock() // ロックを取得
+ if n := len(re.machine); n > 0 { // キャッシュにmachineがあるかチェック
+ z := re.machine[n-1] // 最後の要素を取得
+ re.machine = re.machine[:n-1] // スライスから削除
+ re.mu.Unlock() // ロックを解放
+ return z // 取得したmachineを返す
+ }
+ re.mu.Unlock() // キャッシュが空の場合、ロックを解放
z := progMachine(re.prog) // 新しいmachineを生成
z.re = re
return z
@@ -187,7 +193,9 @@ func (re *Regexp) put(z *machine) {
// grow to the maximum number of simultaneous matches
// run using re. (The cache empties when re gets garbage collected.)
func (re *Regexp) put(z *machine) {
- re.machinePool.Put(z)
+ re.mu.Lock() // ロックを取得
+ re.machine = append(re.machine, z) // machineをスライスに追加
+ re.mu.Unlock() // ロックを解放
}
// MustCompile is like Compile but panics if the expression cannot be parsed.
コアとなるコードの解説
Regexp
構造体
type Regexp struct {
subexpNames []string
longest bool
// cache of machines for running regexp
mu sync.Mutex
machine []*machine
}
machinePool sync.Pool
が削除され、代わりにmu sync.Mutex
とmachine []*machine
が追加されました。mu
は、machine
スライスへのアクセスを保護するためのミューテックスです。これにより、複数のGoroutineが同時にmachine
スライスにアクセスしても、データ競合が発生しないようにします。machine
は、再利用可能な*machine
オブジェクトを格納するためのスライスです。このスライスが、正規表現のマッチングに使用されるmachine
オブジェクトのカスタムキャッシュとして機能します。
get()
メソッド
func (re *Regexp) get() *machine {
re.mu.Lock() // ロックを取得
if n := len(re.machine); n > 0 { // キャッシュにmachineがあるかチェック
z := re.machine[n-1] // 最後の要素を取得
re.machine = re.machine[:n-1] // スライスから削除
re.mu.Unlock() // ロックを解放
return z // 取得したmachineを返す
}
re.mu.Unlock() // キャッシュが空の場合、ロックを解放
z := progMachine(re.prog) // 新しいmachineを生成
z.re = re
return z
}
- このメソッドは、正規表現のマッチングに使用する
machine
オブジェクトを取得します。 - まず
re.mu.Lock()
を呼び出してミューテックスをロックし、machine
スライスへの排他的アクセスを確保します。 if n := len(re.machine); n > 0
で、machine
スライスに再利用可能なmachine
オブジェクトが存在するかどうかを確認します。- もし存在する場合(
n > 0
)、スライスの最後の要素(re.machine[n-1]
)を取り出し、その要素をスライスから削除します(re.machine = re.machine[:n-1]
)。その後、re.mu.Unlock()
でロックを解放し、取得したmachine
オブジェクトを返します。 - もしスライスが空の場合、
re.mu.Unlock()
でロックを解放し、progMachine(re.prog)
を呼び出して新しいmachine
オブジェクトを生成して返します。
put()
メソッド
func (re *Regexp) put(z *machine) {
re.mu.Lock() // ロックを取得
re.machine = append(re.machine, z) // machineをスライスに追加
re.mu.Unlock() // ロックを解放
}
- このメソッドは、使用済みの
machine
オブジェクトをキャッシュに戻します。 re.mu.Lock()
を呼び出してミューテックスをロックします。re.machine = append(re.machine, z)
で、引数で渡されたmachine
オブジェクトz
をmachine
スライスの末尾に追加します。- 最後に
re.mu.Unlock()
でロックを解放します。
これらの変更により、regexp
パッケージはsync.Pool
の自動的なクリアメカニズムに依存せず、より直接的かつ制御された方法でmachine
オブジェクトの再利用を行うようになりました。これにより、ガベージコレクションのオーバーヘッドをより効果的に削減し、正規表現のマッチングパフォーマンスを安定させることが期待されます。
関連リンク
- Go言語の
sync.Pool
に関する公式ドキュメント: https://pkg.go.dev/sync#Pool - Go言語の
sync.Mutex
に関する公式ドキュメント: https://pkg.go.dev/sync#Mutex - 元のコミット (CL 44150043): https://golang.org/cl/44150043
- このコミット (CL 48190043): https://golang.org/cl/48190043
- 関連するIssue #4720: https://golang.org/issue/4720
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
sync.Pool
の利用に関する一般的な議論やベストプラクティスに関する記事 (例: "When to use sync.Pool" などで検索)- Goの正規表現エンジンの内部実装に関する資料 (例: "Go regexp package internals" などで検索)
- GitHubのgolang/goリポジトリのコミット履歴と関連するIssue。
- Web検索: "golang sync.Pool garbage collection behavior"