ガベージコレクタ(以降GC)は、ガベージを常に収集するのではなく、一定の間隔で収集します。 コードが一部のデータ構造にメモリを割り当ててから、それらを解放する場合(円内など)、これにより、新しいメモリを割り当てるために
runtime
をOSに強制するなど、GCに負荷がかかります。 想像してみてください: ピース (たとえば
[]byte
)を選択し、それを操作し、リリースします。 GCが「スリープから復帰」してこのピースを収集するまでに時間がかかります。 この時点で同じピースをもう1つ割り当て、OSに十分なメモリが既に割り当てられていない場合、アプリケーションはOSにさらにメモリを要求するように強制されます。 アプリケーションの時間に応じて、OSからのメモリ要求は永遠に続きます。 そして、まさにこの時点で、どこかほこりっぽい、その古い「うまくいった」 作品の時間を待っています。
どうする?
- プールを作成する
- ピースの状態をリセットする
- 使用済みのピースをプールに入れる
- プールから新しいピースを取ります
プールを作成
import( "sync" ) var bytesPool = sync.Pool{ New: func() interface{} { return []byte{} }, } /* `New` . , `New` `nil` - . `interace{}` - . - . */
状態をリセット
// ary []byte ary = ary[:0] // len, cap
プールに入れる
/* , ( ) - ; : 2048 500-800 , - */ const maxCap = 1024 if cap(ary) <= maxCap { // bytesPool.Put(ary) }
プールから取る
nextAry := bytesPool.Get().([]byte)
Newに関する説明
New
関数は、空の
[]byte{}
作成し、これらの変換も
interface{}
、またはその逆も行います。
[]byte
場合、
append
を使用してビルドする可能性が最も高いため、基本的にこのアプローチは採算が取れません。
-
[]byte
ゼロの容量を作成しています -
interface{}
への二重変換、またはその逆 -
append
はまだ新しいピースを作成します -
append
はnil
にフィードできappend
が、 []バイトのようにしかできません(インターフェイス{}はできません)
プール全体を扱う2つの関数を作成する方がはるかに便利です。
// func getBytes() (b []byte) { ifc := bytesPool.Get() if ifc != nil { b = ifc.([]byte) } return } // func putBytes(b []byte) { if cap(b) <= maxCap { b = b[:0] // bytesPool.Put(b) } }
覚えている
-
sync.Pool
万能薬でsync.Pool
ません - goroutinoセーフプール
- プールは、GCの最初の起動時に必ずしもデータを解放するわけではありませんが、いつでも解放できます
- プールサイズを決定および設定する方法はありません
- プールのオーバーフローを処理する必要はありません
- どこにいてもプールをプールする必要はまったくありません。それは、パッケージ内だけでなく他のパッケージでも、複数の共有オブジェクトが共有されたときにショックアブソーバーとして作成されました
- おそらく、GCを支援する必要性/機会が明白になる状況があるか、またはそうなるでしょう
- バッファチャネルを使用して、サイズが制限されたプールが作成されます
プールを使用する良い例は、 fmtパッケージです。 109行目から150行目まで。