[インデックス 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"