whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
// Copyright 2019 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 syncmap provides a concurrent map implementation.
|
||||
// This was the prototype for sync.Map which was added to the standard library's
|
||||
// sync package in Go 1.9. https://golang.org/pkg/sync/#Map.
|
||||
package syncmap
|
||||
|
||||
import "sync" // home to the standard library's sync.map implementation as of Go 1.9
|
||||
|
||||
// Map is a concurrent map with amortized-constant-time loads, stores, and deletes.
|
||||
// It is safe for multiple goroutines to call a Map's methods concurrently.
|
||||
//
|
||||
// The zero Map is valid and empty.
|
||||
//
|
||||
// A Map must not be copied after first use.
|
||||
type Map = sync.Map
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright 2016 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 syncmap_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sync/syncmap"
|
||||
)
|
||||
|
||||
type bench struct {
|
||||
setup func(*testing.B, mapInterface)
|
||||
perG func(b *testing.B, pb *testing.PB, i int, m mapInterface)
|
||||
}
|
||||
|
||||
func benchMap(b *testing.B, bench bench) {
|
||||
for _, m := range [...]mapInterface{&DeepCopyMap{}, &RWMutexMap{}, &syncmap.Map{}} {
|
||||
b.Run(fmt.Sprintf("%T", m), func(b *testing.B) {
|
||||
m = reflect.New(reflect.TypeOf(m).Elem()).Interface().(mapInterface)
|
||||
if bench.setup != nil {
|
||||
bench.setup(b, m)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
var i int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
id := int(atomic.AddInt64(&i, 1) - 1)
|
||||
bench.perG(b, pb, id*b.N, m)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLoadMostlyHits(b *testing.B) {
|
||||
const hits, misses = 1023, 1
|
||||
|
||||
benchMap(b, bench{
|
||||
setup: func(_ *testing.B, m mapInterface) {
|
||||
for i := 0; i < hits; i++ {
|
||||
m.LoadOrStore(i, i)
|
||||
}
|
||||
// Prime the map to get it into a steady state.
|
||||
for i := 0; i < hits*2; i++ {
|
||||
m.Load(i % hits)
|
||||
}
|
||||
},
|
||||
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
for ; pb.Next(); i++ {
|
||||
m.Load(i % (hits + misses))
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkLoadMostlyMisses(b *testing.B) {
|
||||
const hits, misses = 1, 1023
|
||||
|
||||
benchMap(b, bench{
|
||||
setup: func(_ *testing.B, m mapInterface) {
|
||||
for i := 0; i < hits; i++ {
|
||||
m.LoadOrStore(i, i)
|
||||
}
|
||||
// Prime the map to get it into a steady state.
|
||||
for i := 0; i < hits*2; i++ {
|
||||
m.Load(i % hits)
|
||||
}
|
||||
},
|
||||
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
for ; pb.Next(); i++ {
|
||||
m.Load(i % (hits + misses))
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkLoadOrStoreBalanced(b *testing.B) {
|
||||
const hits, misses = 128, 128
|
||||
|
||||
benchMap(b, bench{
|
||||
setup: func(b *testing.B, m mapInterface) {
|
||||
if _, ok := m.(*DeepCopyMap); ok {
|
||||
b.Skip("DeepCopyMap has quadratic running time.")
|
||||
}
|
||||
for i := 0; i < hits; i++ {
|
||||
m.LoadOrStore(i, i)
|
||||
}
|
||||
// Prime the map to get it into a steady state.
|
||||
for i := 0; i < hits*2; i++ {
|
||||
m.Load(i % hits)
|
||||
}
|
||||
},
|
||||
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
for ; pb.Next(); i++ {
|
||||
j := i % (hits + misses)
|
||||
if j < hits {
|
||||
if _, ok := m.LoadOrStore(j, i); !ok {
|
||||
b.Fatalf("unexpected miss for %v", j)
|
||||
}
|
||||
} else {
|
||||
if v, loaded := m.LoadOrStore(i, i); loaded {
|
||||
b.Fatalf("failed to store %v: existing value %v", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkLoadOrStoreUnique(b *testing.B) {
|
||||
benchMap(b, bench{
|
||||
setup: func(b *testing.B, m mapInterface) {
|
||||
if _, ok := m.(*DeepCopyMap); ok {
|
||||
b.Skip("DeepCopyMap has quadratic running time.")
|
||||
}
|
||||
},
|
||||
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
for ; pb.Next(); i++ {
|
||||
m.LoadOrStore(i, i)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkLoadOrStoreCollision(b *testing.B) {
|
||||
benchMap(b, bench{
|
||||
setup: func(_ *testing.B, m mapInterface) {
|
||||
m.LoadOrStore(0, 0)
|
||||
},
|
||||
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
for ; pb.Next(); i++ {
|
||||
m.LoadOrStore(0, 0)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRange(b *testing.B) {
|
||||
const mapSize = 1 << 10
|
||||
|
||||
benchMap(b, bench{
|
||||
setup: func(_ *testing.B, m mapInterface) {
|
||||
for i := 0; i < mapSize; i++ {
|
||||
m.Store(i, i)
|
||||
}
|
||||
},
|
||||
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
for ; pb.Next(); i++ {
|
||||
m.Range(func(_, _ interface{}) bool { return true })
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkAdversarialAlloc tests performance when we store a new value
|
||||
// immediately whenever the map is promoted to clean and otherwise load a
|
||||
// unique, missing key.
|
||||
//
|
||||
// This forces the Load calls to always acquire the map's mutex.
|
||||
func BenchmarkAdversarialAlloc(b *testing.B) {
|
||||
benchMap(b, bench{
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
var stores, loadsSinceStore int64
|
||||
for ; pb.Next(); i++ {
|
||||
m.Load(i)
|
||||
if loadsSinceStore++; loadsSinceStore > stores {
|
||||
m.LoadOrStore(i, stores)
|
||||
loadsSinceStore = 0
|
||||
stores++
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkAdversarialDelete tests performance when we periodically delete
|
||||
// one key and add a different one in a large map.
|
||||
//
|
||||
// This forces the Load calls to always acquire the map's mutex and periodically
|
||||
// makes a full copy of the map despite changing only one entry.
|
||||
func BenchmarkAdversarialDelete(b *testing.B) {
|
||||
const mapSize = 1 << 10
|
||||
|
||||
benchMap(b, bench{
|
||||
setup: func(_ *testing.B, m mapInterface) {
|
||||
for i := 0; i < mapSize; i++ {
|
||||
m.Store(i, i)
|
||||
}
|
||||
},
|
||||
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
for ; pb.Next(); i++ {
|
||||
m.Load(i)
|
||||
|
||||
if i%mapSize == 0 {
|
||||
m.Range(func(k, _ interface{}) bool {
|
||||
m.Delete(k)
|
||||
return false
|
||||
})
|
||||
m.Store(i, i)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright 2016 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 syncmap_test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// This file contains reference map implementations for unit-tests.
|
||||
|
||||
// mapInterface is the interface Map implements.
|
||||
type mapInterface interface {
|
||||
Load(interface{}) (interface{}, bool)
|
||||
Store(key, value interface{})
|
||||
LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
|
||||
Delete(interface{})
|
||||
Range(func(key, value interface{}) (shouldContinue bool))
|
||||
}
|
||||
|
||||
// RWMutexMap is an implementation of mapInterface using a sync.RWMutex.
|
||||
type RWMutexMap struct {
|
||||
mu sync.RWMutex
|
||||
dirty map[interface{}]interface{}
|
||||
}
|
||||
|
||||
func (m *RWMutexMap) Load(key interface{}) (value interface{}, ok bool) {
|
||||
m.mu.RLock()
|
||||
value, ok = m.dirty[key]
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (m *RWMutexMap) Store(key, value interface{}) {
|
||||
m.mu.Lock()
|
||||
if m.dirty == nil {
|
||||
m.dirty = make(map[interface{}]interface{})
|
||||
}
|
||||
m.dirty[key] = value
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *RWMutexMap) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
|
||||
m.mu.Lock()
|
||||
actual, loaded = m.dirty[key]
|
||||
if !loaded {
|
||||
actual = value
|
||||
if m.dirty == nil {
|
||||
m.dirty = make(map[interface{}]interface{})
|
||||
}
|
||||
m.dirty[key] = value
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return actual, loaded
|
||||
}
|
||||
|
||||
func (m *RWMutexMap) Delete(key interface{}) {
|
||||
m.mu.Lock()
|
||||
delete(m.dirty, key)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *RWMutexMap) Range(f func(key, value interface{}) (shouldContinue bool)) {
|
||||
m.mu.RLock()
|
||||
keys := make([]interface{}, 0, len(m.dirty))
|
||||
for k := range m.dirty {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
for _, k := range keys {
|
||||
v, ok := m.Load(k)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyMap is an implementation of mapInterface using a Mutex and
|
||||
// atomic.Value. It makes deep copies of the map on every write to avoid
|
||||
// acquiring the Mutex in Load.
|
||||
type DeepCopyMap struct {
|
||||
mu sync.Mutex
|
||||
clean atomic.Value
|
||||
}
|
||||
|
||||
func (m *DeepCopyMap) Load(key interface{}) (value interface{}, ok bool) {
|
||||
clean, _ := m.clean.Load().(map[interface{}]interface{})
|
||||
value, ok = clean[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (m *DeepCopyMap) Store(key, value interface{}) {
|
||||
m.mu.Lock()
|
||||
dirty := m.dirty()
|
||||
dirty[key] = value
|
||||
m.clean.Store(dirty)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *DeepCopyMap) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
|
||||
clean, _ := m.clean.Load().(map[interface{}]interface{})
|
||||
actual, loaded = clean[key]
|
||||
if loaded {
|
||||
return actual, loaded
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
// Reload clean in case it changed while we were waiting on m.mu.
|
||||
clean, _ = m.clean.Load().(map[interface{}]interface{})
|
||||
actual, loaded = clean[key]
|
||||
if !loaded {
|
||||
dirty := m.dirty()
|
||||
dirty[key] = value
|
||||
actual = value
|
||||
m.clean.Store(dirty)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return actual, loaded
|
||||
}
|
||||
|
||||
func (m *DeepCopyMap) Delete(key interface{}) {
|
||||
m.mu.Lock()
|
||||
dirty := m.dirty()
|
||||
delete(dirty, key)
|
||||
m.clean.Store(dirty)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *DeepCopyMap) Range(f func(key, value interface{}) (shouldContinue bool)) {
|
||||
clean, _ := m.clean.Load().(map[interface{}]interface{})
|
||||
for k, v := range clean {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *DeepCopyMap) dirty() map[interface{}]interface{} {
|
||||
clean, _ := m.clean.Load().(map[interface{}]interface{})
|
||||
dirty := make(map[interface{}]interface{}, len(clean)+1)
|
||||
for k, v := range clean {
|
||||
dirty[k] = v
|
||||
}
|
||||
return dirty
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright 2016 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 syncmap_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"golang.org/x/sync/syncmap"
|
||||
)
|
||||
|
||||
type mapOp string
|
||||
|
||||
const (
|
||||
opLoad = mapOp("Load")
|
||||
opStore = mapOp("Store")
|
||||
opLoadOrStore = mapOp("LoadOrStore")
|
||||
opDelete = mapOp("Delete")
|
||||
)
|
||||
|
||||
var mapOps = [...]mapOp{opLoad, opStore, opLoadOrStore, opDelete}
|
||||
|
||||
// mapCall is a quick.Generator for calls on mapInterface.
|
||||
type mapCall struct {
|
||||
op mapOp
|
||||
k, v interface{}
|
||||
}
|
||||
|
||||
func (c mapCall) apply(m mapInterface) (interface{}, bool) {
|
||||
switch c.op {
|
||||
case opLoad:
|
||||
return m.Load(c.k)
|
||||
case opStore:
|
||||
m.Store(c.k, c.v)
|
||||
return nil, false
|
||||
case opLoadOrStore:
|
||||
return m.LoadOrStore(c.k, c.v)
|
||||
case opDelete:
|
||||
m.Delete(c.k)
|
||||
return nil, false
|
||||
default:
|
||||
panic("invalid mapOp")
|
||||
}
|
||||
}
|
||||
|
||||
type mapResult struct {
|
||||
value interface{}
|
||||
ok bool
|
||||
}
|
||||
|
||||
func randValue(r *rand.Rand) interface{} {
|
||||
b := make([]byte, r.Intn(4))
|
||||
for i := range b {
|
||||
b[i] = 'a' + byte(rand.Intn(26))
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (mapCall) Generate(r *rand.Rand, size int) reflect.Value {
|
||||
c := mapCall{op: mapOps[rand.Intn(len(mapOps))], k: randValue(r)}
|
||||
switch c.op {
|
||||
case opStore, opLoadOrStore:
|
||||
c.v = randValue(r)
|
||||
}
|
||||
return reflect.ValueOf(c)
|
||||
}
|
||||
|
||||
func applyCalls(m mapInterface, calls []mapCall) (results []mapResult, final map[interface{}]interface{}) {
|
||||
for _, c := range calls {
|
||||
v, ok := c.apply(m)
|
||||
results = append(results, mapResult{v, ok})
|
||||
}
|
||||
|
||||
final = make(map[interface{}]interface{})
|
||||
m.Range(func(k, v interface{}) bool {
|
||||
final[k] = v
|
||||
return true
|
||||
})
|
||||
|
||||
return results, final
|
||||
}
|
||||
|
||||
func applyMap(calls []mapCall) ([]mapResult, map[interface{}]interface{}) {
|
||||
return applyCalls(new(syncmap.Map), calls)
|
||||
}
|
||||
|
||||
func applyRWMutexMap(calls []mapCall) ([]mapResult, map[interface{}]interface{}) {
|
||||
return applyCalls(new(RWMutexMap), calls)
|
||||
}
|
||||
|
||||
func applyDeepCopyMap(calls []mapCall) ([]mapResult, map[interface{}]interface{}) {
|
||||
return applyCalls(new(DeepCopyMap), calls)
|
||||
}
|
||||
|
||||
func TestMapMatchesRWMutex(t *testing.T) {
|
||||
if err := quick.CheckEqual(applyMap, applyRWMutexMap, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapMatchesDeepCopy(t *testing.T) {
|
||||
if err := quick.CheckEqual(applyMap, applyDeepCopyMap, nil); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentRange(t *testing.T) {
|
||||
const mapSize = 1 << 10
|
||||
|
||||
m := new(syncmap.Map)
|
||||
for n := int64(1); n <= mapSize; n++ {
|
||||
m.Store(n, n)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
defer func() {
|
||||
close(done)
|
||||
wg.Wait()
|
||||
}()
|
||||
for g := int64(runtime.GOMAXPROCS(0)); g > 0; g-- {
|
||||
r := rand.New(rand.NewSource(g))
|
||||
wg.Add(1)
|
||||
go func(g int64) {
|
||||
defer wg.Done()
|
||||
for i := int64(0); ; i++ {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
for n := int64(1); n < mapSize; n++ {
|
||||
if r.Int63n(mapSize) == 0 {
|
||||
m.Store(n, n*i*g)
|
||||
} else {
|
||||
m.Load(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}(g)
|
||||
}
|
||||
|
||||
iters := 1 << 10
|
||||
if testing.Short() {
|
||||
iters = 16
|
||||
}
|
||||
for n := iters; n > 0; n-- {
|
||||
seen := make(map[int64]bool, mapSize)
|
||||
|
||||
m.Range(func(ki, vi interface{}) bool {
|
||||
k, v := ki.(int64), vi.(int64)
|
||||
if v%k != 0 {
|
||||
t.Fatalf("while Storing multiples of %v, Range saw value %v", k, v)
|
||||
}
|
||||
if seen[k] {
|
||||
t.Fatalf("Range visited key %v twice", k)
|
||||
}
|
||||
seen[k] = true
|
||||
return true
|
||||
})
|
||||
|
||||
if len(seen) != mapSize {
|
||||
t.Fatalf("Range visited %v elements of %v-element Map", len(seen), mapSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user