github.com/blong14/gache@v0.0.0-20240124023949-89416fd8bbfa/internal/map/skiplist/map_test.go (about) 1 package skiplist_test 2 3 import ( 4 "fmt" 5 "strconv" 6 "sync" 7 "sync/atomic" 8 "testing" 9 "time" 10 11 gskl "github.com/blong14/gache/internal/map/skiplist" 12 ) 13 14 type test struct { 15 setup func(*testing.T, *gskl.SkipList) 16 run func(t *testing.T, m *gskl.SkipList) 17 teardown func(*testing.T, *gskl.SkipList) func() 18 } 19 20 func testMap(t *testing.T, name string, test test) { 21 t.Run(fmt.Sprintf("skip list test %s", name), func(t *testing.T) { 22 t.Parallel() 23 m := gskl.New() 24 if test.setup != nil { 25 test.setup(t, m) 26 } 27 test.run(t, m) 28 if test.teardown != nil { 29 t.Cleanup(func() { 30 test.teardown(t, m) 31 }) 32 } 33 }) 34 } 35 36 func TestHeight(t *testing.T) { 37 expected := 20 38 testMap(t, "height", test{ 39 setup: func(t *testing.T, m *gskl.SkipList) { 40 for i := 0; i < expected; i++ { 41 err := m.Set( 42 []byte(fmt.Sprintf("key_%d", i)), []byte(fmt.Sprintf("value__%d", i))) 43 if err != nil { 44 t.Fail() 45 } 46 } 47 }, 48 run: func(t *testing.T, m *gskl.SkipList) { 49 actual := m.Height() 50 if actual > uint64(expected) { 51 t.Errorf("w %d g %d", expected, actual) 52 } 53 }, 54 }) 55 } 56 57 func TestCount(t *testing.T) { 58 expected := 100 59 testMap(t, "count", test{ 60 setup: func(t *testing.T, m *gskl.SkipList) { 61 for i := 0; i < expected; i++ { 62 err := m.Set( 63 []byte(fmt.Sprintf("key_%d", i)), []byte(fmt.Sprintf("value__%d", i))) 64 if err != nil { 65 t.Fail() 66 } 67 } 68 }, 69 run: func(t *testing.T, m *gskl.SkipList) { 70 actual := m.Count() 71 if actual != uint64(expected) { 72 t.Errorf("w %d g %d", expected, actual) 73 } 74 }, 75 }) 76 } 77 78 func TestGetAndSet(t *testing.T) { 79 count := 50_000 80 testMap(t, "get and set", test{ 81 run: func(t *testing.T, m *gskl.SkipList) { 82 start := time.Now() 83 var wg sync.WaitGroup 84 for i := 0; i < count; i++ { 85 wg.Add(1) 86 go func(indx int) { 87 defer wg.Done() 88 k := []byte(fmt.Sprintf("key-%d", indx)) 89 err := m.Set(k, []byte(fmt.Sprintf("value__%d", indx))) 90 if err != nil { 91 t.Error(err) 92 } 93 }(i) 94 } 95 wg.Wait() 96 t.Logf("%s", time.Since(start)) 97 for i := 0; i < count; i++ { 98 wg.Add(1) 99 go func(idx int) { 100 defer wg.Done() 101 k := []byte(fmt.Sprintf("key-%d", idx)) 102 if _, ok := m.Get(k); !ok { 103 t.Errorf("missing rawKey key-%d", idx) 104 } 105 }(i) 106 } 107 wg.Wait() 108 // m.Print() 109 t.Logf("%s", time.Since(start)) 110 }, 111 }) 112 } 113 114 type bench struct { 115 setup func(*testing.B, *gskl.SkipList) 116 perG func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) 117 teardown func(*testing.B, *gskl.SkipList) func() 118 } 119 120 func benchMap(b *testing.B, bench bench) { 121 b.Run("skip list benchmark", func(b *testing.B) { 122 m := gskl.New() 123 if bench.setup != nil { 124 bench.setup(b, m) 125 } 126 b.ReportAllocs() 127 b.ResetTimer() 128 var i int64 129 b.RunParallel(func(pb *testing.PB) { 130 id := int(atomic.AddInt64(&i, 1) - 1) 131 bench.perG(b, pb, id*b.N, m) 132 }) 133 if bench.teardown != nil { 134 b.Cleanup(func() { 135 bench.teardown(b, m) 136 }) 137 } 138 }) 139 } 140 141 func BenchmarkSkiplist_LoadMostlyHits(b *testing.B) { 142 const hits, misses = 1023, 1 143 144 benchMap(b, bench{ 145 setup: func(b *testing.B, m *gskl.SkipList) { 146 b.StopTimer() 147 for i := 0; i < hits; i++ { 148 v := strconv.Itoa(i) 149 err := m.Set([]byte(v), []byte(v)) 150 if err != nil { 151 b.Fail() 152 } 153 } 154 }, 155 perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) { 156 b.StartTimer() 157 for ; pb.Next(); i++ { 158 m.Get([]byte(strconv.Itoa(i % (hits + misses)))) 159 } 160 }, 161 }) 162 } 163 164 func BenchmarkSkiplist_XLoadMostlyHits(b *testing.B) { 165 const hits, misses = 1023, 1 166 mmap := make(map[string]string) 167 var mtx sync.RWMutex 168 benchMap(b, bench{ 169 setup: func(b *testing.B, m *gskl.SkipList) { 170 mtx.Lock() 171 for i := 0; i < hits; i++ { 172 key := strconv.Itoa(i) 173 mmap[key] = key 174 } 175 mtx.Unlock() 176 }, 177 perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) { 178 for ; pb.Next(); i++ { 179 k := strconv.Itoa(i % (hits + misses)) 180 mtx.RLock() 181 _ = mmap[k] 182 mtx.RUnlock() 183 } 184 }, 185 }) 186 } 187 188 func BenchmarkSkiplist_LoadMostlyMisses(b *testing.B) { 189 const hits, misses = 1, 1023 190 benchMap(b, bench{ 191 setup: func(_ *testing.B, m *gskl.SkipList) { 192 for i := 0; i < hits; i++ { 193 key := []byte(strconv.Itoa(i)) 194 if err := m.Set(key, key); err != nil { 195 b.Fail() 196 } 197 } 198 }, 199 perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) { 200 for ; pb.Next(); i++ { 201 m.Get([]byte(strconv.Itoa(i % (hits + misses)))) 202 } 203 }, 204 }) 205 } 206 207 func BenchmarkSkiplist_LoadOrStoreBalanced(b *testing.B) { 208 const hits, misses = 1023, 1023 209 value := []byte("value") 210 benchMap(b, bench{ 211 setup: func(b *testing.B, m *gskl.SkipList) { 212 for i := 0; i < hits; i++ { 213 key := []byte(strconv.Itoa(i)) 214 if err := m.Set(key, value); err != nil { 215 b.Fail() 216 } 217 } 218 }, 219 perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) { 220 for ; pb.Next(); i++ { 221 j := i % (hits + misses) 222 key := []byte(strconv.Itoa(j)) 223 if j < hits { 224 if _, ok := m.Get(key); !ok { 225 b.Fatalf("unexpected miss for %v", j) 226 } 227 } else { 228 if err := m.Set(key, value); err != nil { 229 b.Error(err) 230 } 231 } 232 } 233 }, 234 }) 235 } 236 237 func BenchmarkSkiplist_LoadOrStoreUnique(b *testing.B) { 238 const hits = 1023 239 value := []byte("value") 240 benchMap(b, bench{ 241 perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) { 242 for ; pb.Next(); i++ { 243 j := i % hits 244 key := []byte(strconv.Itoa(j)) 245 if _, ok := m.Get(key); !ok { 246 if err := m.Set(key, value); err != nil { 247 b.Error(err) 248 } 249 } 250 } 251 }, 252 }) 253 } 254 255 func BenchmarkSkiplist_LoadOrStoreCollision(b *testing.B) { 256 const hits = 1023 257 value := []byte("value") 258 benchMap(b, bench{ 259 setup: func(b *testing.B, m *gskl.SkipList) { 260 for i := 0; i < hits; i++ { 261 key := []byte(strconv.Itoa(i)) 262 if err := m.Set(key, value); err != nil { 263 b.Fail() 264 } 265 } 266 }, 267 perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) { 268 for ; pb.Next(); i++ { 269 j := i % hits 270 key := []byte(strconv.Itoa(j)) 271 if _, ok := m.Get(key); ok { 272 if err := m.Set(key, value); err != nil { 273 b.Error(err) 274 } 275 } else { 276 b.Errorf("unexpected miss %s", key) 277 } 278 } 279 }, 280 }) 281 } 282 283 func BenchmarkSkiplist_AdversarialAlloc(b *testing.B) { 284 value := []byte("value") 285 benchMap(b, bench{ 286 perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) { 287 var stores, loadsSinceStore int64 288 for ; pb.Next(); i++ { 289 key := []byte(strconv.Itoa(i)) 290 m.Get(key) 291 if loadsSinceStore++; loadsSinceStore > stores { 292 if _, ok := m.Get(key); !ok { 293 err := m.Set(key, value) 294 if err != nil { 295 b.Error(err) 296 } 297 } 298 loadsSinceStore = 0 299 stores++ 300 } 301 } 302 }, 303 }) 304 } 305 306 func BenchmarkSkiplist_Range(b *testing.B) { 307 const mapSize = 1 << 10 308 value := []byte("") 309 benchMap(b, bench{ 310 setup: func(_ *testing.B, m *gskl.SkipList) { 311 for i := 0; i < mapSize; i++ { 312 key := []byte(strconv.Itoa(i)) 313 err := m.Set(key, value) 314 if err != nil { 315 b.Fail() 316 } 317 } 318 }, 319 perG: func(b *testing.B, pb *testing.PB, i int, m *gskl.SkipList) { 320 for ; pb.Next(); i++ { 321 m.Range(func(_, _ []byte) bool { return true }) 322 } 323 }, 324 }) 325 }