]> Cypherpunks repositories - gostls13.git/commitdiff
sync: add Pool type
authorBrad Fitzpatrick <bradfitz@golang.org>
Wed, 18 Dec 2013 19:08:34 +0000 (11:08 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Wed, 18 Dec 2013 19:08:34 +0000 (11:08 -0800)
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
src/pkg/sync/pool.go [new file with mode: 0644]
src/pkg/sync/pool_test.go [new file with mode: 0644]

index 8b8a3e52b69aa9064868fb3f71b74aa0c0a07da9..f329787044fa7118d3ca69bd6a5ca2c677078dec 100644 (file)
@@ -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 (file)
index 0000000..3facba9
--- /dev/null
@@ -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 (file)
index 0000000..e4aeda4
--- /dev/null
@@ -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()
+}