2016-07-21 5 views
9

私はGoでHTTPサーバを実装しました。Golangでメモリプールを実装する方法

リクエストごとに、特定の構造体に対して何百ものオブジェクトを作成する必要があります。そのような構造体が10個あります。したがって、Goの実装ごとに要求が完了した後、ガベージコレクションされます。

このように、要求ごとに多くのメモリが割り当てられ、割り当てが解除されます。

は、代わりに私は、私はプールから取得し、要求が提供された後に戻ってそれらを置く割り当て側からのパフォーマンスだけでなく、要求の初めにも、GC側

を向上させるためにプールメモリを実装したかった

プールの実装側から

  1. 特定のタイプの構造体のメモリを割り当てる方法と割り当てを解除する方法はありますか?
  2. このメモリに割り当てられた情報と他の情報がどのように追跡されていますか?

メモリの割り当てと割り当て解除の際のパフォーマンスを改善するためのその他の提案はありますか?事前に

+5

[sync.Pool'](https://golang.org/pkg/sync/#Pool)がstdlibにあります。それ以外に、 "フリーリスト"を実装することは、Goに固有のものではないテクニックです。 – JimB

答えて

14

注:

多くは一時的オブジェクトのための高速、良い実装ですsync.Poolを使用することをお勧めします。ただし、sync.Poolはプールされたオブジェクトが保持されることを保証しません。そのドキュメントからの引用:

プールに格納された任意の項目が通知ことなく、いつでも自動的に除去することができます。このときプールに唯一の参照が保持されていれば、アイテムの割り当てが解除される可能性があります。

あなたは(あなたのケースによっては、より配分につながる可能性がある)Poolであなたのオブジェクトがガベージコレクションを取得したくない場合は、チャネルのバッファ内の値がでないように、以下に提示するソリューションは、優れていますガベージコレクション。あなたのオブジェクトが実際に大きすぎてメモリプールが正当化されない場合、プールチャネルのオーバーヘッドは償却されます。


最も単純なメモリプール "実装"は、バッファされたチャネルです。

大きなオブジェクトのメモリプールが必要な場合を考えてみましょう。そのような高価なオブジェクトの値へのポインタを保持するバッファされたチャネルを作成し、必要なときにはプール(チャネル)から1つを受け取ります。あなたがそれを使用し終わったら、それをプールに戻してください(チャンネルを送ってください)。誤ってオブジェクトを失うのを防ぐには(例えばパニックの場合)、それらを戻す際にdeferのステートメントを使用します。

のが私たちの大きなオブジェクトの種類としてこれを使用してみましょう:プールの作成

type BigObject struct { 
    Id  int 
    Something string 
} 

は次のとおりです。

pool := make(chan *BigObject, 10) 

プールのサイズは、単にチャネルのバッファのサイズです。

高価なオブジェクトのポインタを持つプール(これはオプションで、最後にノートを参照してください)充填:多くのゴルーチンでプールを使用して

for i := 0; i < cap(pool); i++ { 
    bo := &BigObject{Id: i} 
    pool <- bo 
} 

を:

wg := sync.WaitGroup{} 
for i := 0; i < 100; i++ { 
    wg.Add(1) 
    go func() { 
     defer wg.Done() 
     bo := <-pool 
     defer func() { pool <- bo }() 
     fmt.Println("Using", bo.Id) 
     fmt.Println("Releasing", bo.Id) 
    }() 
} 

wg.Wait() 

Go Playground上でそれを試してみてください。

「プールされた」オブジェクトがすべて使用されている場合、この実装はブロックされることに注意してください。

var bo *BigObject 
select { 
case bo = <-pool: // Try to get one from the pool 
default: // All in use, create a new, temporary: 
    bo = &BigObject{Id:-1} 
} 

そして、この場合には、あなたがプールに戻ってそれを配置する必要はありません:あなたはこれをしたくない場合は、すべてが使用中である場合は、新しいオブジェクトを作成する強制的にselectを使用することができます。それとも、部屋がプールにあるかどうselectで再び、ブロックせずに、プールにすべてのバック入れしようとすることもできます:

select { 
case pool <- bo: // Try to put back into the pool 
default: // Pool is full, will be garbage collected 
} 

注:以前はオプションでプールを充填

selectを使用してプールから値を取得/プールしようとすると、プールは最初は空になることがあります。

リクエスト間で情報が漏洩していないことを確認する必要があります。他の要求に設定されている共有オブジェクトでフィールドと値を使用しないようにしてください。

+5

このメソッドは 'sync.Pool'よりもかなり遅いので注意してください。 – OneOfOne

+3

@OneOfOneはい、しかし 'sync.Pool'はプールされた値の保持を保証しません。編集済みの回答を参照してください。 – icza

+0

良い点、意味があります。 – OneOfOne

10

これは、@ JimBが言及したsync.Poolの実装です。オブジェクトをプールに戻すにはdeferの使用を覚えておいてください。

package main 

import "sync" 

type Something struct { 
    Name string 
} 

var pool = sync.Pool{ 
    New: func() interface{} { 
     return &Something{} 
    }, 
} 

func main() { 
    s := pool.Get().(*Something) 
    defer pool.Put(s) 
    s.Name = "hello" 
    // use the object 
} 
+1

これは、オブジェクトインスタンスの作成に制限を作成しません。 –

関連する問題