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

[インデックス 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.Poolmachineオブジェクトを頻繁にクリアしてしまうと、期待されるパフォーマンス改善が得られないか、むしろ悪化する可能性がありました。

元のコミット(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オブジェクトのキャッシュメカニズムを変更します。

変更点

  1. Regexp構造体の変更:

    • machinePool sync.Pool // of *machine フィールドが削除されました。
    • 代わりに、mu sync.Mutexmachine []*machine フィールドが追加されました。
      • muは、machineスライスへのアクセスを同期するためのミューテックスです。
      • machineは、再利用可能な*machineオブジェクトを保持するスライスです。
  2. get()メソッドの変更:

    • 以前はre.machinePool.Get()を呼び出してプールからmachineオブジェクトを取得していました。
    • 変更後、re.mu.Lock()でロックを取得し、re.machineスライスから最後の要素を取り出して返します。スライスが空の場合は、新しいmachineオブジェクトを生成します。最後にre.mu.Unlock()でロックを解放します。
    • このカスタム実装により、sync.PoolのGCによるクリアの影響を受けずに、machineオブジェクトを明示的に管理できます。
  3. put()メソッドの変更:

    • 以前はre.machinePool.Put(z)を呼び出してmachineオブジェクトをプールに戻していました。
    • 変更後、re.mu.Lock()でロックを取得し、re.machineスライスにmachineオブジェクトを追加します。最後にre.mu.Unlock()でロックを解放します。

なぜsync.Poolからカスタムキャッシュへ?

この変更の主な理由は、sync.Poolの特性、特にガベージコレクション(GC)のサイクルでプールされたオブジェクトがクリアされる可能性がある点にあります。

  • GCによるクリアの問題: sync.Poolは、GCが実行されるたびにプール内のオブジェクトをクリアする可能性があります。これは、プールが「キャッシュ」ではなく「一時的なオブジェクトの再利用メカニズム」として設計されているためです。regexpmachineオブジェクトは、マッチングのたびに頻繁に取得・解放されるため、もしプールが頻繁にクリアされると、オブジェクトの再利用率が低下し、アロケーションと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.Mutexmachine []*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オブジェクトzmachineスライスの末尾に追加します。
  • 最後にre.mu.Unlock()でロックを解放します。

これらの変更により、regexpパッケージはsync.Poolの自動的なクリアメカニズムに依存せず、より直接的かつ制御された方法でmachineオブジェクトの再利用を行うようになりました。これにより、ガベージコレクションのオーバーヘッドをより効果的に削減し、正規表現のマッチングパフォーマンスを安定させることが期待されます。

関連リンク

参考にした情報源リンク

  • 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.Poolmachineオブジェクトを頻繁にクリアしてしまうと、期待されるパフォーマンス改善が得られないか、むしろ悪化する可能性がありました。

元のコミット(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オブジェクトのキャッシュメカニズムを変更します。

変更点

  1. Regexp構造体の変更:

    • machinePool sync.Pool // of *machine フィールドが削除されました。
    • 代わりに、mu sync.Mutexmachine []*machine フィールドが追加されました。
      • muは、machineスライスへのアクセスを同期するためのミューテックスです。
      • machineは、再利用可能な*machineオブジェクトを保持するスライスです。
  2. get()メソッドの変更:

    • 以前はre.machinePool.Get()を呼び出してプールからmachineオブジェクトを取得していました。
    • 変更後、re.mu.Lock()でロックを取得し、re.machineスライスから最後の要素を取り出して返します。スライスが空の場合は、新しいmachineオブジェクトを生成します。最後にre.mu.Unlock()でロックを解放します。
    • このカスタム実装により、sync.PoolのGCによるクリアの影響を受けずに、machineオブジェクトを明示的に管理できます。
  3. put()メソッドの変更:

    • 以前はre.machinePool.Put(z)を呼び出してmachineオブジェクトをプールに戻していました。
    • 変更後、re.mu.Lock()でロックを取得し、re.machineスライスにmachineオブジェクトを追加します。最後にre.mu.Unlock()でロックを解放します。

なぜsync.Poolからカスタムキャッシュへ?

この変更の主な理由は、sync.Poolの特性、特にガベージコレクション(GC)のサイクルでプールされたオブジェクトがクリアされる可能性がある点にあります。

  • GCによるクリアの問題: sync.Poolは、GCが実行されるたびにプール内のオブジェクトをクリアする可能性があります。これは、プールが「キャッシュ」ではなく「一時的なオブジェクトの再利用メカニズム」として設計されているためです。regexpmachineオブジェクトは、マッチングのたびに頻繁に取得・解放されるため、もしプールが頻繁にクリアされると、オブジェクトの再利用率が低下し、アロケーションと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.Mutexmachine []*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オブジェクトzmachineスライスの末尾に追加します。
  • 最後にre.mu.Unlock()でロックを解放します。

これらの変更により、regexpパッケージはsync.Poolの自動的なクリアメカニズムに依存せず、より直接的かつ制御された方法でmachineオブジェクトの再利用を行うようになりました。これにより、ガベージコレクションのオーバーヘッドをより効果的に削減し、正規表現のマッチングパフォーマンスを安定させることが期待されます。

関連リンク

参考にした情報源リンク

  • 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"