github.com/lrita/cmap@v0.0.0-20231108122212-cb084a67f554/cmap_bench_test.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cmap_test 6 7 import ( 8 "fmt" 9 "reflect" 10 "sync" 11 "sync/atomic" 12 "testing" 13 14 "github.com/lrita/cmap" 15 ) 16 17 type bench struct { 18 setup func(*testing.B, mapInterface) 19 perG func(b *testing.B, pb *testing.PB, i int, m mapInterface) 20 } 21 22 func benchMap(b *testing.B, bench bench) { 23 for _, m := range [...]mapInterface{&DeepCopyMap{}, &RWMutexMap{}, &sync.Map{}, &cmap.Cmap{}} { 24 b.Run(fmt.Sprintf("%T", m), func(b *testing.B) { 25 m = reflect.New(reflect.TypeOf(m).Elem()).Interface().(mapInterface) 26 if bench.setup != nil { 27 bench.setup(b, m) 28 } 29 30 b.ResetTimer() 31 32 var i int64 33 b.RunParallel(func(pb *testing.PB) { 34 id := int(atomic.AddInt64(&i, 1) - 1) 35 bench.perG(b, pb, id*b.N, m) 36 }) 37 }) 38 } 39 } 40 41 func BenchmarkLoadMostlyHits(b *testing.B) { 42 const hits, misses = 1023, 1 43 44 benchMap(b, bench{ 45 setup: func(_ *testing.B, m mapInterface) { 46 for i := 0; i < hits; i++ { 47 m.LoadOrStore(i, i) 48 } 49 // Prime the map to get it into a steady state. 50 for i := 0; i < hits*2; i++ { 51 m.Load(i % hits) 52 } 53 }, 54 55 perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) { 56 for ; pb.Next(); i++ { 57 m.Load(i % (hits + misses)) 58 } 59 }, 60 }) 61 } 62 63 func BenchmarkLoadMostlyMisses(b *testing.B) { 64 const hits, misses = 1, 1023 65 66 benchMap(b, bench{ 67 setup: func(_ *testing.B, m mapInterface) { 68 for i := 0; i < hits; i++ { 69 m.LoadOrStore(i, i) 70 } 71 // Prime the map to get it into a steady state. 72 for i := 0; i < hits*2; i++ { 73 m.Load(i % hits) 74 } 75 }, 76 77 perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) { 78 for ; pb.Next(); i++ { 79 m.Load(i % (hits + misses)) 80 } 81 }, 82 }) 83 } 84 85 func BenchmarkLoadOrStoreBalanced(b *testing.B) { 86 const hits, misses = 128, 128 87 88 benchMap(b, bench{ 89 setup: func(b *testing.B, m mapInterface) { 90 if _, ok := m.(*DeepCopyMap); ok { 91 b.Skip("DeepCopyMap has quadratic running time.") 92 } 93 for i := 0; i < hits; i++ { 94 m.LoadOrStore(i, i) 95 } 96 // Prime the map to get it into a steady state. 97 for i := 0; i < hits*2; i++ { 98 m.Load(i % hits) 99 } 100 }, 101 102 perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) { 103 for ; pb.Next(); i++ { 104 j := i % (hits + misses) 105 if j < hits { 106 if _, ok := m.LoadOrStore(j, i); !ok { 107 b.Fatalf("unexpected miss for %v", j) 108 } 109 } else { 110 if v, loaded := m.LoadOrStore(i, i); loaded { 111 b.Fatalf("failed to store %v: existing value %v", i, v) 112 } 113 } 114 } 115 }, 116 }) 117 } 118 119 func BenchmarkLoadOrStoreUnique(b *testing.B) { 120 benchMap(b, bench{ 121 setup: func(b *testing.B, m mapInterface) { 122 if _, ok := m.(*DeepCopyMap); ok { 123 b.Skip("DeepCopyMap has quadratic running time.") 124 } 125 }, 126 127 perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) { 128 for ; pb.Next(); i++ { 129 m.LoadOrStore(i, i) 130 } 131 }, 132 }) 133 } 134 135 func BenchmarkLoadOrStoreCollision(b *testing.B) { 136 benchMap(b, bench{ 137 setup: func(_ *testing.B, m mapInterface) { 138 m.LoadOrStore(0, 0) 139 }, 140 141 perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) { 142 for ; pb.Next(); i++ { 143 m.LoadOrStore(0, 0) 144 } 145 }, 146 }) 147 } 148 149 func BenchmarkRange(b *testing.B) { 150 const mapSize = 1 << 10 151 152 benchMap(b, bench{ 153 setup: func(_ *testing.B, m mapInterface) { 154 for i := 0; i < mapSize; i++ { 155 m.Store(i, i) 156 } 157 }, 158 159 perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) { 160 for ; pb.Next(); i++ { 161 m.Range(func(_, _ interface{}) bool { return true }) 162 } 163 }, 164 }) 165 } 166 167 // BenchmarkAdversarialAlloc tests performance when we store a new value 168 // immediately whenever the map is promoted to clean and otherwise load a 169 // unique, missing key. 170 // 171 // This forces the Load calls to always acquire the map's mutex. 172 func BenchmarkAdversarialAlloc(b *testing.B) { 173 benchMap(b, bench{ 174 perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) { 175 var stores, loadsSinceStore int64 176 for ; pb.Next(); i++ { 177 m.Load(i) 178 if loadsSinceStore++; loadsSinceStore > stores { 179 m.LoadOrStore(i, stores) 180 loadsSinceStore = 0 181 stores++ 182 } 183 } 184 }, 185 }) 186 } 187 188 // BenchmarkAdversarialDelete tests performance when we periodically delete 189 // one key and add a different one in a large map. 190 // 191 // This forces the Load calls to always acquire the map's mutex and periodically 192 // makes a full copy of the map despite changing only one entry. 193 func BenchmarkAdversarialDelete(b *testing.B) { 194 const mapSize = 1 << 10 195 196 benchMap(b, bench{ 197 setup: func(_ *testing.B, m mapInterface) { 198 for i := 0; i < mapSize; i++ { 199 m.Store(i, i) 200 } 201 }, 202 203 perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) { 204 for ; pb.Next(); i++ { 205 m.Load(i) 206 207 if i%mapSize == 0 { 208 m.Range(func(k, _ interface{}) bool { 209 m.Delete(k) 210 return false 211 }) 212 m.Store(i, i) 213 } 214 } 215 }, 216 }) 217 }