From 8c6ef061e3c189e3ac90a451d5680aab9d142618 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 18 Dec 2013 11:08:34 -0800 Subject: [PATCH] sync: add Pool type Adds the Pool type and docs, and use it in fmt. This is a temporary implementation, until Dmitry makes it fast. Uses the API proposal from Russ in http://goo.gl/cCKeb2 but adds an optional New field, as used in fmt and elsewhere. Almost all callers want that. Update #4720 R=golang-dev, rsc, cshapiro, iant, r, dvyukov, khr CC=golang-dev https://golang.org/cl/41860043 --- src/pkg/runtime/mgc0.c | 34 ++++++++- src/pkg/sync/pool.go | 65 ++++++++++++++++ src/pkg/sync/pool_test.go | 154 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 src/pkg/sync/pool.go create mode 100644 src/pkg/sync/pool_test.go diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c index 8b8a3e52b6..f329787044 100644 --- a/src/pkg/runtime/mgc0.c +++ b/src/pkg/runtime/mgc0.c @@ -42,6 +42,36 @@ enum { BitsEface = 3, }; +static struct +{ + Lock; + void* head; +} pools; + +void +sync·runtime_registerPool(void **p) +{ + runtime·lock(&pools); + p[0] = pools.head; + pools.head = p; + runtime·unlock(&pools); +} + +static void +clearpools(void) +{ + void **p, **next; + + for(p = pools.head; p != nil; p = next) { + next = p[0]; + p[0] = nil; // next + p[1] = nil; // slice + p[2] = nil; + p[3] = nil; + } + pools.head = nil; +} + // Bits in per-word bitmap. // #defines because enum might not be able to hold the values. // @@ -2089,7 +2119,9 @@ runtime·gc(int32 force) a.start_time = runtime·nanotime(); m->gcing = 1; runtime·stoptheworld(); - + + clearpools(); + // Run gc on the g0 stack. We do this so that the g stack // we're currently running on will no longer change. Cuts // the root set down a bit (g0 stacks are not scanned, and diff --git a/src/pkg/sync/pool.go b/src/pkg/sync/pool.go new file mode 100644 index 0000000000..3facba98fa --- /dev/null +++ b/src/pkg/sync/pool.go @@ -0,0 +1,65 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sync + +// A Pool is a set of temporary objects that may be individually saved +// and retrieved. +// +// Any item stored in the Pool may be removed automatically by the +// implementation at any time without notification. +// If the Pool holds the only reference when this happens, the item +// might be deallocated. +// +// A Pool is safe for use by multiple goroutines simultaneously. +// +// This is an experimental package and might not be released. +type Pool struct { + next *Pool // for use by runtime. must be first. + list []interface{} // offset known to runtime + mu Mutex // guards list + + // New optionally specifies a function to generate + // a value when Get would otherwise return nil. + // It may not be changed concurrently with calls to Get. + New func() interface{} +} + +func runtime_registerPool(*Pool) + +// Put adds x to the pool. +func (p *Pool) Put(x interface{}) { + if x == nil { + return + } + p.mu.Lock() + if p.list == nil { + runtime_registerPool(p) + } + p.list = append(p.list, x) + p.mu.Unlock() +} + +// Get selects an arbitrary item from the Pool, removes it from the +// Pool, and returns it to the caller. +// Get may choose to ignore the pool and treat it as empty. +// Callers should not assume any relation between values passed to Put and +// the values returned by Get. +// +// If Get would otherwise return nil and p.New is non-nil, Get returns +// the result of calling p.New. +func (p *Pool) Get() interface{} { + p.mu.Lock() + var x interface{} + if n := len(p.list); n > 0 { + x = p.list[n-1] + p.list[n-1] = nil // Just to be safe + p.list = p.list[:n-1] + } + p.mu.Unlock() + if x == nil && p.New != nil { + x = p.New() + } + return x +} diff --git a/src/pkg/sync/pool_test.go b/src/pkg/sync/pool_test.go new file mode 100644 index 0000000000..e4aeda4be4 --- /dev/null +++ b/src/pkg/sync/pool_test.go @@ -0,0 +1,154 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sync_test + +import ( + "runtime" + "runtime/debug" + . "sync" + "sync/atomic" + "testing" + "time" + "unsafe" +) + +func TestPool(t *testing.T) { + // disable GC so we can control when it happens. + defer debug.SetGCPercent(debug.SetGCPercent(-1)) + var p Pool + if p.Get() != nil { + t.Fatal("expected empty") + } + p.Put("a") + p.Put("b") + if g := p.Get(); g != "b" { + t.Fatalf("got %#v; want b", g) + } + if g := p.Get(); g != "a" { + t.Fatalf("got %#v; want a", g) + } + if g := p.Get(); g != nil { + t.Fatalf("got %#v; want nil", g) + } + + p.Put("c") + debug.SetGCPercent(100) // to allow following GC to actually run + runtime.GC() + if g := p.Get(); g != nil { + t.Fatalf("got %#v; want nil after GC", g) + } +} + +func TestPoolNew(t *testing.T) { + // disable GC so we can control when it happens. + defer debug.SetGCPercent(debug.SetGCPercent(-1)) + + i := 0 + p := Pool{ + New: func() interface{} { + i++ + return i + }, + } + if v := p.Get(); v != 1 { + t.Fatalf("got %v; want 1", v) + } + if v := p.Get(); v != 2 { + t.Fatalf("got %v; want 2", v) + } + p.Put(42) + if v := p.Get(); v != 42 { + t.Fatalf("got %v; want 42", v) + } + if v := p.Get(); v != 3 { + t.Fatalf("got %v; want 3", v) + } +} + +// Test that Pool does not hold pointers to previously cached +// resources +func TestPoolGC(t *testing.T) { + var p Pool + var fin uint32 + const N = 100 + for i := 0; i < N; i++ { + v := new(int) + runtime.SetFinalizer(v, func(vv *int) { + atomic.AddUint32(&fin, 1) + }) + p.Put(v) + } + for i := 0; i < N; i++ { + p.Get() + } + for i := 0; i < 5; i++ { + runtime.GC() + time.Sleep(time.Millisecond) + // 1 pointer can remain on stack or elsewhere + if atomic.LoadUint32(&fin) >= N-1 { + return + } + } + t.Fatalf("only %v out of %v resources are finalized", + atomic.LoadUint32(&fin), N) +} + +func TestPoolStress(t *testing.T) { + const P = 10 + N := int(1e6) + if testing.Short() { + N /= 100 + } + var p Pool + done := make(chan bool) + for i := 0; i < P; i++ { + go func() { + var v interface{} = 0 + for j := 0; j < N; j++ { + if v == nil { + v = 0 + } + p.Put(v) + v = p.Get() + if v != nil && v.(int) != 0 { + t.Fatalf("expect 0, got %v", v) + } + } + done <- true + }() + } + for i := 0; i < P; i++ { + <-done + } +} + +func BenchmarkPool(b *testing.B) { + procs := runtime.GOMAXPROCS(-1) + var dec func() bool + if unsafe.Sizeof(b.N) == 8 { + n := int64(b.N) + dec = func() bool { + return atomic.AddInt64(&n, -1) >= 0 + } + } else { + n := int32(b.N) + dec = func() bool { + return atomic.AddInt32(&n, -1) >= 0 + } + } + var p Pool + var wg WaitGroup + for i := 0; i < procs; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for dec() { + p.Put(1) + p.Get() + } + }() + } + wg.Wait() +}