github.com/puzpuzpuz/xsync/v2@v2.5.2-0.20231021165734-92b8269e19a9/map_test.go (about) 1 package xsync_test 2 3 import ( 4 "fmt" 5 "math" 6 "math/bits" 7 "math/rand" 8 "strconv" 9 "sync" 10 "sync/atomic" 11 "testing" 12 "time" 13 "unsafe" 14 15 . "github.com/puzpuzpuz/xsync/v2" 16 ) 17 18 const ( 19 // number of entries to use in benchmarks 20 benchmarkNumEntries = 1_000 21 // key prefix used in benchmarks 22 benchmarkKeyPrefix = "what_a_looooooooooooooooooooooong_key_prefix_" 23 ) 24 25 var benchmarkCases = []struct { 26 name string 27 readPercentage int 28 }{ 29 {"reads=100%", 100}, // 100% loads, 0% stores, 0% deletes 30 {"reads=99%", 99}, // 99% loads, 0.5% stores, 0.5% deletes 31 {"reads=90%", 90}, // 90% loads, 5% stores, 5% deletes 32 {"reads=75%", 75}, // 75% loads, 12.5% stores, 12.5% deletes 33 } 34 35 var benchmarkKeys []string 36 37 func init() { 38 benchmarkKeys = make([]string, benchmarkNumEntries) 39 for i := 0; i < benchmarkNumEntries; i++ { 40 benchmarkKeys[i] = benchmarkKeyPrefix + strconv.Itoa(i) 41 } 42 } 43 44 func runParallel(b *testing.B, benchFn func(pb *testing.PB)) { 45 b.ResetTimer() 46 start := time.Now() 47 b.RunParallel(benchFn) 48 opsPerSec := float64(b.N) / float64(time.Since(start).Seconds()) 49 b.ReportMetric(opsPerSec, "ops/s") 50 } 51 52 func TestMap_BucketStructSize(t *testing.T) { 53 size := unsafe.Sizeof(BucketPadded{}) 54 if size != 64 { 55 t.Fatalf("size of 64B (one cache line) is expected, got: %d", size) 56 } 57 } 58 59 func TestMap_UniqueValuePointers_Int(t *testing.T) { 60 EnableAssertions() 61 m := NewMap() 62 v := 42 63 m.Store("foo", v) 64 m.Store("foo", v) 65 DisableAssertions() 66 } 67 68 func TestMap_UniqueValuePointers_Struct(t *testing.T) { 69 type foo struct{} 70 EnableAssertions() 71 m := NewMap() 72 v := foo{} 73 m.Store("foo", v) 74 m.Store("foo", v) 75 DisableAssertions() 76 } 77 78 func TestMap_UniqueValuePointers_Pointer(t *testing.T) { 79 type foo struct{} 80 EnableAssertions() 81 m := NewMap() 82 v := &foo{} 83 m.Store("foo", v) 84 m.Store("foo", v) 85 DisableAssertions() 86 } 87 88 func TestMap_UniqueValuePointers_Slice(t *testing.T) { 89 EnableAssertions() 90 m := NewMap() 91 v := make([]int, 13) 92 m.Store("foo", v) 93 m.Store("foo", v) 94 DisableAssertions() 95 } 96 97 func TestMap_UniqueValuePointers_String(t *testing.T) { 98 EnableAssertions() 99 m := NewMap() 100 v := "bar" 101 m.Store("foo", v) 102 m.Store("foo", v) 103 DisableAssertions() 104 } 105 106 func TestMap_UniqueValuePointers_Nil(t *testing.T) { 107 EnableAssertions() 108 m := NewMap() 109 m.Store("foo", nil) 110 m.Store("foo", nil) 111 DisableAssertions() 112 } 113 114 func TestMap_MissingEntry(t *testing.T) { 115 m := NewMap() 116 v, ok := m.Load("foo") 117 if ok { 118 t.Fatalf("value was not expected: %v", v) 119 } 120 if deleted, loaded := m.LoadAndDelete("foo"); loaded { 121 t.Fatalf("value was not expected %v", deleted) 122 } 123 if actual, loaded := m.LoadOrStore("foo", "bar"); loaded { 124 t.Fatalf("value was not expected %v", actual) 125 } 126 } 127 128 func TestMap_EmptyStringKey(t *testing.T) { 129 m := NewMap() 130 m.Store("", "foobar") 131 v, ok := m.Load("") 132 if !ok { 133 t.Fatal("value was expected") 134 } 135 if vs, ok := v.(string); ok && vs != "foobar" { 136 t.Fatalf("value does not match: %v", v) 137 } 138 } 139 140 func TestMapStore_NilValue(t *testing.T) { 141 m := NewMap() 142 m.Store("foo", nil) 143 v, ok := m.Load("foo") 144 if !ok { 145 t.Fatal("nil value was expected") 146 } 147 if v != nil { 148 t.Fatalf("value was not nil: %v", v) 149 } 150 } 151 152 func TestMapLoadOrStore_NilValue(t *testing.T) { 153 m := NewMap() 154 m.LoadOrStore("foo", nil) 155 v, loaded := m.LoadOrStore("foo", nil) 156 if !loaded { 157 t.Fatal("nil value was expected") 158 } 159 if v != nil { 160 t.Fatalf("value was not nil: %v", v) 161 } 162 } 163 164 func TestMapLoadOrStore_NonNilValue(t *testing.T) { 165 type foo struct{} 166 m := NewMap() 167 newv := &foo{} 168 v, loaded := m.LoadOrStore("foo", newv) 169 if loaded { 170 t.Fatal("no value was expected") 171 } 172 if v != newv { 173 t.Fatalf("value does not match: %v", v) 174 } 175 newv2 := &foo{} 176 v, loaded = m.LoadOrStore("foo", newv2) 177 if !loaded { 178 t.Fatal("value was expected") 179 } 180 if v != newv { 181 t.Fatalf("value does not match: %v", v) 182 } 183 } 184 185 func TestMapLoadAndStore_NilValue(t *testing.T) { 186 m := NewMap() 187 m.LoadAndStore("foo", nil) 188 v, loaded := m.LoadAndStore("foo", nil) 189 if !loaded { 190 t.Fatal("nil value was expected") 191 } 192 if v != nil { 193 t.Fatalf("value was not nil: %v", v) 194 } 195 v, loaded = m.Load("foo") 196 if !loaded { 197 t.Fatal("nil value was expected") 198 } 199 if v != nil { 200 t.Fatalf("value was not nil: %v", v) 201 } 202 } 203 204 func TestMapLoadAndStore_NonNilValue(t *testing.T) { 205 type foo struct{} 206 m := NewMap() 207 v1 := &foo{} 208 v, loaded := m.LoadAndStore("foo", v1) 209 if loaded { 210 t.Fatal("no value was expected") 211 } 212 if v != v1 { 213 t.Fatalf("value does not match: %v", v) 214 } 215 v2 := 2 216 v, loaded = m.LoadAndStore("foo", v2) 217 if !loaded { 218 t.Fatal("value was expected") 219 } 220 if v != v1 { 221 t.Fatalf("value does not match: %v", v) 222 } 223 v, loaded = m.Load("foo") 224 if !loaded { 225 t.Fatal("value was expected") 226 } 227 if v != v2 { 228 t.Fatalf("value does not match: %v", v) 229 } 230 } 231 232 func TestMapRange(t *testing.T) { 233 const numEntries = 1000 234 m := NewMap() 235 for i := 0; i < numEntries; i++ { 236 m.Store(strconv.Itoa(i), i) 237 } 238 iters := 0 239 met := make(map[string]int) 240 m.Range(func(key string, value interface{}) bool { 241 if key != strconv.Itoa(value.(int)) { 242 t.Fatalf("got unexpected key/value for iteration %d: %v/%v", iters, key, value) 243 return false 244 } 245 met[key] += 1 246 iters++ 247 return true 248 }) 249 if iters != numEntries { 250 t.Fatalf("got unexpected number of iterations: %d", iters) 251 } 252 for i := 0; i < numEntries; i++ { 253 if c := met[strconv.Itoa(i)]; c != 1 { 254 t.Fatalf("met key %d multiple times: %d", i, c) 255 } 256 } 257 } 258 259 func TestMapRange_FalseReturned(t *testing.T) { 260 m := NewMap() 261 for i := 0; i < 100; i++ { 262 m.Store(strconv.Itoa(i), i) 263 } 264 iters := 0 265 m.Range(func(key string, value interface{}) bool { 266 iters++ 267 return iters != 13 268 }) 269 if iters != 13 { 270 t.Fatalf("got unexpected number of iterations: %d", iters) 271 } 272 } 273 274 func TestMapRange_NestedDelete(t *testing.T) { 275 const numEntries = 256 276 m := NewMap() 277 for i := 0; i < numEntries; i++ { 278 m.Store(strconv.Itoa(i), i) 279 } 280 m.Range(func(key string, value interface{}) bool { 281 m.Delete(key) 282 return true 283 }) 284 for i := 0; i < numEntries; i++ { 285 if _, ok := m.Load(strconv.Itoa(i)); ok { 286 t.Fatalf("value found for %d", i) 287 } 288 } 289 } 290 291 func TestMapStore(t *testing.T) { 292 const numEntries = 128 293 m := NewMap() 294 for i := 0; i < numEntries; i++ { 295 m.Store(strconv.Itoa(i), i) 296 } 297 for i := 0; i < numEntries; i++ { 298 v, ok := m.Load(strconv.Itoa(i)) 299 if !ok { 300 t.Fatalf("value not found for %d", i) 301 } 302 if vi, ok := v.(int); ok && vi != i { 303 t.Fatalf("values do not match for %d: %v", i, v) 304 } 305 } 306 } 307 308 func TestMapLoadOrStore(t *testing.T) { 309 const numEntries = 1000 310 m := NewMap() 311 for i := 0; i < numEntries; i++ { 312 m.Store(strconv.Itoa(i), i) 313 } 314 for i := 0; i < numEntries; i++ { 315 if _, loaded := m.LoadOrStore(strconv.Itoa(i), i); !loaded { 316 t.Fatalf("value not found for %d", i) 317 } 318 } 319 } 320 321 func TestMapLoadOrCompute(t *testing.T) { 322 const numEntries = 1000 323 m := NewMap() 324 for i := 0; i < numEntries; i++ { 325 v, loaded := m.LoadOrCompute(strconv.Itoa(i), func() interface{} { 326 return i 327 }) 328 if loaded { 329 t.Fatalf("value not computed for %d", i) 330 } 331 if vi, ok := v.(int); ok && vi != i { 332 t.Fatalf("values do not match for %d: %v", i, v) 333 } 334 } 335 for i := 0; i < numEntries; i++ { 336 v, loaded := m.LoadOrCompute(strconv.Itoa(i), func() interface{} { 337 return i 338 }) 339 if !loaded { 340 t.Fatalf("value not loaded for %d", i) 341 } 342 if vi, ok := v.(int); ok && vi != i { 343 t.Fatalf("values do not match for %d: %v", i, v) 344 } 345 } 346 } 347 348 func TestMapLoadOrCompute_FunctionCalledOnce(t *testing.T) { 349 m := NewMap() 350 for i := 0; i < 100; { 351 m.LoadOrCompute(strconv.Itoa(i), func() (v interface{}) { 352 v, i = i, i+1 353 return v 354 }) 355 } 356 357 m.Range(func(k string, v interface{}) bool { 358 if vi, ok := v.(int); !ok || strconv.Itoa(vi) != k { 359 t.Fatalf("%sth key is not equal to value %d", k, v) 360 } 361 return true 362 }) 363 } 364 365 func TestMapCompute(t *testing.T) { 366 var zeroedV interface{} 367 m := NewMap() 368 // Store a new value. 369 v, ok := m.Compute("foobar", func(oldValue interface{}, loaded bool) (newValue interface{}, delete bool) { 370 if oldValue != zeroedV { 371 t.Fatalf("oldValue should be empty interface{} when computing a new value: %d", oldValue) 372 } 373 if loaded { 374 t.Fatal("loaded should be false when computing a new value") 375 } 376 newValue = 42 377 delete = false 378 return 379 }) 380 if v.(int) != 42 { 381 t.Fatalf("v should be 42 when computing a new value: %d", v) 382 } 383 if !ok { 384 t.Fatal("ok should be true when computing a new value") 385 } 386 // Update an existing value. 387 v, ok = m.Compute("foobar", func(oldValue interface{}, loaded bool) (newValue interface{}, delete bool) { 388 if oldValue.(int) != 42 { 389 t.Fatalf("oldValue should be 42 when updating the value: %d", oldValue) 390 } 391 if !loaded { 392 t.Fatal("loaded should be true when updating the value") 393 } 394 newValue = oldValue.(int) + 42 395 delete = false 396 return 397 }) 398 if v.(int) != 84 { 399 t.Fatalf("v should be 84 when updating the value: %d", v) 400 } 401 if !ok { 402 t.Fatal("ok should be true when updating the value") 403 } 404 // Delete an existing value. 405 v, ok = m.Compute("foobar", func(oldValue interface{}, loaded bool) (newValue interface{}, delete bool) { 406 if oldValue != 84 { 407 t.Fatalf("oldValue should be 84 when deleting the value: %d", oldValue) 408 } 409 if !loaded { 410 t.Fatal("loaded should be true when deleting the value") 411 } 412 delete = true 413 return 414 }) 415 if v.(int) != 84 { 416 t.Fatalf("v should be 84 when deleting the value: %d", v) 417 } 418 if ok { 419 t.Fatal("ok should be false when deleting the value") 420 } 421 // Try to delete a non-existing value. Notice different key. 422 v, ok = m.Compute("barbaz", func(oldValue interface{}, loaded bool) (newValue interface{}, delete bool) { 423 var zeroedV interface{} 424 if oldValue != zeroedV { 425 t.Fatalf("oldValue should be empty interface{} when trying to delete a non-existing value: %d", oldValue) 426 } 427 if loaded { 428 t.Fatal("loaded should be false when trying to delete a non-existing value") 429 } 430 // We're returning a non-zero value, but the map should ignore it. 431 newValue = 42 432 delete = true 433 return 434 }) 435 if v != zeroedV { 436 t.Fatalf("v should be empty interface{} when trying to delete a non-existing value: %d", v) 437 } 438 if ok { 439 t.Fatal("ok should be false when trying to delete a non-existing value") 440 } 441 } 442 443 func TestMapStoreThenDelete(t *testing.T) { 444 const numEntries = 1000 445 m := NewMap() 446 for i := 0; i < numEntries; i++ { 447 m.Store(strconv.Itoa(i), i) 448 } 449 for i := 0; i < numEntries; i++ { 450 m.Delete(strconv.Itoa(i)) 451 if _, ok := m.Load(strconv.Itoa(i)); ok { 452 t.Fatalf("value was not expected for %d", i) 453 } 454 } 455 } 456 457 func TestMapStoreThenLoadAndDelete(t *testing.T) { 458 const numEntries = 1000 459 m := NewMap() 460 for i := 0; i < numEntries; i++ { 461 m.Store(strconv.Itoa(i), i) 462 } 463 for i := 0; i < numEntries; i++ { 464 if v, loaded := m.LoadAndDelete(strconv.Itoa(i)); !loaded || v.(int) != i { 465 t.Fatalf("value was not found or different for %d: %v", i, v) 466 } 467 if _, ok := m.Load(strconv.Itoa(i)); ok { 468 t.Fatalf("value was not expected for %d", i) 469 } 470 } 471 } 472 473 func sizeBasedOnRange(m *Map) int { 474 size := 0 475 m.Range(func(key string, value interface{}) bool { 476 size++ 477 return true 478 }) 479 return size 480 } 481 482 func TestMapSize(t *testing.T) { 483 const numEntries = 1000 484 m := NewMap() 485 size := m.Size() 486 if size != 0 { 487 t.Fatalf("zero size expected: %d", size) 488 } 489 expectedSize := 0 490 for i := 0; i < numEntries; i++ { 491 m.Store(strconv.Itoa(i), i) 492 expectedSize++ 493 size := m.Size() 494 if size != expectedSize { 495 t.Fatalf("size of %d was expected, got: %d", expectedSize, size) 496 } 497 rsize := sizeBasedOnRange(m) 498 if size != rsize { 499 t.Fatalf("size does not match number of entries in Range: %v, %v", size, rsize) 500 } 501 } 502 for i := 0; i < numEntries; i++ { 503 m.Delete(strconv.Itoa(i)) 504 expectedSize-- 505 size := m.Size() 506 if size != expectedSize { 507 t.Fatalf("size of %d was expected, got: %d", expectedSize, size) 508 } 509 rsize := sizeBasedOnRange(m) 510 if size != rsize { 511 t.Fatalf("size does not match number of entries in Range: %v, %v", size, rsize) 512 } 513 } 514 } 515 516 func TestMapClear(t *testing.T) { 517 const numEntries = 1000 518 m := NewMap() 519 for i := 0; i < numEntries; i++ { 520 m.Store(strconv.Itoa(i), i) 521 } 522 size := m.Size() 523 if size != numEntries { 524 t.Fatalf("size of %d was expected, got: %d", numEntries, size) 525 } 526 m.Clear() 527 size = m.Size() 528 if size != 0 { 529 t.Fatalf("zero size was expected, got: %d", size) 530 } 531 rsize := sizeBasedOnRange(m) 532 if rsize != 0 { 533 t.Fatalf("zero number of entries in Range was expected, got: %d", rsize) 534 } 535 } 536 537 func assertMapCapacity(t *testing.T, m *Map, expectedCap int) { 538 stats := CollectMapStats(m) 539 if stats.Capacity != expectedCap { 540 t.Fatalf("capacity was different from %d: %d", expectedCap, stats.Capacity) 541 } 542 } 543 544 func TestNewMapPresized(t *testing.T) { 545 assertMapCapacity(t, NewMap(), MinMapTableCap) 546 assertMapCapacity(t, NewMapPresized(1000), 1536) 547 assertMapCapacity(t, NewMapPresized(0), MinMapTableCap) 548 assertMapCapacity(t, NewMapPresized(-1), MinMapTableCap) 549 } 550 551 func TestMapResize(t *testing.T) { 552 const numEntries = 100_000 553 m := NewMap() 554 555 for i := 0; i < numEntries; i++ { 556 m.Store(strconv.Itoa(i), i) 557 } 558 stats := CollectMapStats(m) 559 if stats.Size != numEntries { 560 t.Fatalf("size was too small: %d", stats.Size) 561 } 562 expectedCapacity := int(math.RoundToEven(MapLoadFactor+1)) * stats.RootBuckets * EntriesPerMapBucket 563 if stats.Capacity > expectedCapacity { 564 t.Fatalf("capacity was too large: %d, expected: %d", stats.Capacity, expectedCapacity) 565 } 566 if stats.RootBuckets <= MinMapTableLen { 567 t.Fatalf("table was too small: %d", stats.RootBuckets) 568 } 569 if stats.TotalGrowths == 0 { 570 t.Fatalf("non-zero total growths expected: %d", stats.TotalGrowths) 571 } 572 if stats.TotalShrinks > 0 { 573 t.Fatalf("zero total shrinks expected: %d", stats.TotalShrinks) 574 } 575 // This is useful when debugging table resize and occupancy. 576 // Use -v flag to see the output. 577 t.Log(stats.ToString()) 578 579 for i := 0; i < numEntries; i++ { 580 m.Delete(strconv.Itoa(i)) 581 } 582 stats = CollectMapStats(m) 583 if stats.Size > 0 { 584 t.Fatalf("zero size was expected: %d", stats.Size) 585 } 586 expectedCapacity = stats.RootBuckets * EntriesPerMapBucket 587 if stats.Capacity != expectedCapacity { 588 t.Fatalf("capacity was too large: %d, expected: %d", stats.Capacity, expectedCapacity) 589 } 590 if stats.RootBuckets != MinMapTableLen { 591 t.Fatalf("table was too large: %d", stats.RootBuckets) 592 } 593 if stats.TotalShrinks == 0 { 594 t.Fatalf("non-zero total shrinks expected: %d", stats.TotalShrinks) 595 } 596 t.Log(stats.ToString()) 597 } 598 599 func TestMapResize_CounterLenLimit(t *testing.T) { 600 const numEntries = 1_000_000 601 m := NewMap() 602 603 for i := 0; i < numEntries; i++ { 604 m.Store("foo"+strconv.Itoa(i), "bar"+strconv.Itoa(i)) 605 } 606 stats := CollectMapStats(m) 607 if stats.Size != numEntries { 608 t.Fatalf("size was too small: %d", stats.Size) 609 } 610 if stats.CounterLen != MaxMapCounterLen { 611 t.Fatalf("number of counter stripes was too large: %d, expected: %d", 612 stats.CounterLen, MaxMapCounterLen) 613 } 614 } 615 616 func parallelSeqResizer(t *testing.T, m *Map, numEntries int, positive bool, cdone chan bool) { 617 for i := 0; i < numEntries; i++ { 618 if positive { 619 m.Store(strconv.Itoa(i), i) 620 } else { 621 m.Store(strconv.Itoa(-i), -i) 622 } 623 } 624 cdone <- true 625 } 626 627 func TestMapParallelResize_GrowOnly(t *testing.T) { 628 const numEntries = 100_000 629 m := NewMap() 630 cdone := make(chan bool) 631 go parallelSeqResizer(t, m, numEntries, true, cdone) 632 go parallelSeqResizer(t, m, numEntries, false, cdone) 633 // Wait for the goroutines to finish. 634 <-cdone 635 <-cdone 636 // Verify map contents. 637 for i := -numEntries + 1; i < numEntries; i++ { 638 v, ok := m.Load(strconv.Itoa(i)) 639 if !ok { 640 t.Fatalf("value not found for %d", i) 641 } 642 if vi, ok := v.(int); ok && vi != i { 643 t.Fatalf("values do not match for %d: %v", i, v) 644 } 645 } 646 if s := m.Size(); s != 2*numEntries-1 { 647 t.Fatalf("unexpected size: %v", s) 648 } 649 } 650 651 func parallelRandResizer(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 652 r := rand.New(rand.NewSource(time.Now().UnixNano())) 653 for i := 0; i < numIters; i++ { 654 coin := r.Int63n(2) 655 for j := 0; j < numEntries; j++ { 656 if coin == 1 { 657 m.Store(strconv.Itoa(j), j) 658 } else { 659 m.Delete(strconv.Itoa(j)) 660 } 661 } 662 } 663 cdone <- true 664 } 665 666 func TestMapParallelResize(t *testing.T) { 667 const numIters = 1_000 668 const numEntries = 2 * EntriesPerMapBucket * MinMapTableLen 669 m := NewMap() 670 cdone := make(chan bool) 671 go parallelRandResizer(t, m, numIters, numEntries, cdone) 672 go parallelRandResizer(t, m, numIters, numEntries, cdone) 673 // Wait for the goroutines to finish. 674 <-cdone 675 <-cdone 676 // Verify map contents. 677 for i := 0; i < numEntries; i++ { 678 v, ok := m.Load(strconv.Itoa(i)) 679 if !ok { 680 // The entry may be deleted and that's ok. 681 continue 682 } 683 if vi, ok := v.(int); ok && vi != i { 684 t.Fatalf("values do not match for %d: %v", i, v) 685 } 686 } 687 s := m.Size() 688 if s > numEntries { 689 t.Fatalf("unexpected size: %v", s) 690 } 691 rs := sizeBasedOnRange(m) 692 if s != rs { 693 t.Fatalf("size does not match number of entries in Range: %v, %v", s, rs) 694 } 695 } 696 697 func parallelRandClearer(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 698 r := rand.New(rand.NewSource(time.Now().UnixNano())) 699 for i := 0; i < numIters; i++ { 700 coin := r.Int63n(2) 701 for j := 0; j < numEntries; j++ { 702 if coin == 1 { 703 m.Store(strconv.Itoa(j), j) 704 } else { 705 m.Clear() 706 } 707 } 708 } 709 cdone <- true 710 } 711 712 func TestMapParallelClear(t *testing.T) { 713 const numIters = 100 714 const numEntries = 1_000 715 m := NewMap() 716 cdone := make(chan bool) 717 go parallelRandClearer(t, m, numIters, numEntries, cdone) 718 go parallelRandClearer(t, m, numIters, numEntries, cdone) 719 // Wait for the goroutines to finish. 720 <-cdone 721 <-cdone 722 // Verify map size. 723 s := m.Size() 724 if s > numEntries { 725 t.Fatalf("unexpected size: %v", s) 726 } 727 rs := sizeBasedOnRange(m) 728 if s != rs { 729 t.Fatalf("size does not match number of entries in Range: %v, %v", s, rs) 730 } 731 } 732 733 func parallelSeqStorer(t *testing.T, m *Map, storeEach, numIters, numEntries int, cdone chan bool) { 734 for i := 0; i < numIters; i++ { 735 for j := 0; j < numEntries; j++ { 736 if storeEach == 0 || j%storeEach == 0 { 737 m.Store(strconv.Itoa(j), j) 738 // Due to atomic snapshots we must see a "<j>"/j pair. 739 v, ok := m.Load(strconv.Itoa(j)) 740 if !ok { 741 t.Errorf("value was not found for %d", j) 742 break 743 } 744 if vi, ok := v.(int); !ok || vi != j { 745 t.Errorf("value was not expected for %d: %d", j, vi) 746 break 747 } 748 } 749 } 750 } 751 cdone <- true 752 } 753 754 func TestMapParallelStores(t *testing.T) { 755 const numStorers = 4 756 const numIters = 10_000 757 const numEntries = 100 758 m := NewMap() 759 cdone := make(chan bool) 760 for i := 0; i < numStorers; i++ { 761 go parallelSeqStorer(t, m, i, numIters, numEntries, cdone) 762 } 763 // Wait for the goroutines to finish. 764 for i := 0; i < numStorers; i++ { 765 <-cdone 766 } 767 // Verify map contents. 768 for i := 0; i < numEntries; i++ { 769 v, ok := m.Load(strconv.Itoa(i)) 770 if !ok { 771 t.Fatalf("value not found for %d", i) 772 } 773 if vi, ok := v.(int); ok && vi != i { 774 t.Fatalf("values do not match for %d: %v", i, v) 775 } 776 } 777 } 778 779 func parallelRandStorer(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 780 r := rand.New(rand.NewSource(time.Now().UnixNano())) 781 for i := 0; i < numIters; i++ { 782 j := r.Intn(numEntries) 783 if v, loaded := m.LoadOrStore(strconv.Itoa(j), j); loaded { 784 if vi, ok := v.(int); !ok || vi != j { 785 t.Errorf("value was not expected for %d: %d", j, vi) 786 } 787 } 788 } 789 cdone <- true 790 } 791 792 func parallelRandDeleter(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 793 r := rand.New(rand.NewSource(time.Now().UnixNano())) 794 for i := 0; i < numIters; i++ { 795 j := r.Intn(numEntries) 796 if v, loaded := m.LoadAndDelete(strconv.Itoa(j)); loaded { 797 if vi, ok := v.(int); !ok || vi != j { 798 t.Errorf("value was not expected for %d: %d", j, vi) 799 } 800 } 801 } 802 cdone <- true 803 } 804 805 func parallelLoader(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 806 for i := 0; i < numIters; i++ { 807 for j := 0; j < numEntries; j++ { 808 // Due to atomic snapshots we must either see no entry, or a "<j>"/j pair. 809 if v, ok := m.Load(strconv.Itoa(j)); ok { 810 if vi, ok := v.(int); !ok || vi != j { 811 t.Errorf("value was not expected for %d: %d", j, vi) 812 } 813 } 814 } 815 } 816 cdone <- true 817 } 818 819 func TestMapAtomicSnapshot(t *testing.T) { 820 const numIters = 100_000 821 const numEntries = 100 822 m := NewMap() 823 cdone := make(chan bool) 824 // Update or delete random entry in parallel with loads. 825 go parallelRandStorer(t, m, numIters, numEntries, cdone) 826 go parallelRandDeleter(t, m, numIters, numEntries, cdone) 827 go parallelLoader(t, m, numIters, numEntries, cdone) 828 // Wait for the goroutines to finish. 829 for i := 0; i < 3; i++ { 830 <-cdone 831 } 832 } 833 834 func TestMapParallelStoresAndDeletes(t *testing.T) { 835 const numWorkers = 2 836 const numIters = 100_000 837 const numEntries = 1000 838 m := NewMap() 839 cdone := make(chan bool) 840 // Update random entry in parallel with deletes. 841 for i := 0; i < numWorkers; i++ { 842 go parallelRandStorer(t, m, numIters, numEntries, cdone) 843 go parallelRandDeleter(t, m, numIters, numEntries, cdone) 844 } 845 // Wait for the goroutines to finish. 846 for i := 0; i < 2*numWorkers; i++ { 847 <-cdone 848 } 849 } 850 851 func parallelComputer(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 852 for i := 0; i < numIters; i++ { 853 for j := 0; j < numEntries; j++ { 854 m.Compute(strconv.Itoa(j), func(oldValue interface{}, loaded bool) (newValue interface{}, delete bool) { 855 if !loaded { 856 return uint64(1), false 857 } 858 return uint64(oldValue.(uint64) + 1), false 859 }) 860 } 861 } 862 cdone <- true 863 } 864 865 func TestMapParallelComputes(t *testing.T) { 866 const numWorkers = 4 // Also stands for numEntries. 867 const numIters = 10_000 868 m := NewMap() 869 cdone := make(chan bool) 870 for i := 0; i < numWorkers; i++ { 871 go parallelComputer(t, m, numIters, numWorkers, cdone) 872 } 873 // Wait for the goroutines to finish. 874 for i := 0; i < numWorkers; i++ { 875 <-cdone 876 } 877 // Verify map contents. 878 for i := 0; i < numWorkers; i++ { 879 v, ok := m.Load(strconv.Itoa(i)) 880 if !ok { 881 t.Fatalf("value not found for %d", i) 882 } 883 if v.(uint64) != numWorkers*numIters { 884 t.Fatalf("values do not match for %d: %v", i, v) 885 } 886 } 887 } 888 889 func parallelRangeStorer(t *testing.T, m *Map, numEntries int, stopFlag *int64, cdone chan bool) { 890 for { 891 for i := 0; i < numEntries; i++ { 892 m.Store(strconv.Itoa(i), i) 893 } 894 if atomic.LoadInt64(stopFlag) != 0 { 895 break 896 } 897 } 898 cdone <- true 899 } 900 901 func parallelRangeDeleter(t *testing.T, m *Map, numEntries int, stopFlag *int64, cdone chan bool) { 902 for { 903 for i := 0; i < numEntries; i++ { 904 m.Delete(strconv.Itoa(i)) 905 } 906 if atomic.LoadInt64(stopFlag) != 0 { 907 break 908 } 909 } 910 cdone <- true 911 } 912 913 func TestMapParallelRange(t *testing.T) { 914 const numEntries = 10_000 915 m := NewMap() 916 for i := 0; i < numEntries; i++ { 917 m.Store(strconv.Itoa(i), i) 918 } 919 // Start goroutines that would be storing and deleting items in parallel. 920 cdone := make(chan bool) 921 stopFlag := int64(0) 922 go parallelRangeStorer(t, m, numEntries, &stopFlag, cdone) 923 go parallelRangeDeleter(t, m, numEntries, &stopFlag, cdone) 924 // Iterate the map and verify that no duplicate keys were met. 925 met := make(map[string]int) 926 m.Range(func(key string, value interface{}) bool { 927 if key != strconv.Itoa(value.(int)) { 928 t.Fatalf("got unexpected value for key %s: %v", key, value) 929 return false 930 } 931 met[key] += 1 932 return true 933 }) 934 if len(met) == 0 { 935 t.Fatal("no entries were met when iterating") 936 } 937 for k, c := range met { 938 if c != 1 { 939 t.Fatalf("met key %s multiple times: %d", k, c) 940 } 941 } 942 // Make sure that both goroutines finish. 943 atomic.StoreInt64(&stopFlag, 1) 944 <-cdone 945 <-cdone 946 } 947 948 func TestMapTopHashMutex(t *testing.T) { 949 const ( 950 numLockers = 4 951 numIterations = 1000 952 ) 953 var ( 954 activity int32 955 mu uint64 956 ) 957 cdone := make(chan bool) 958 for i := 0; i < numLockers; i++ { 959 go func() { 960 for i := 0; i < numIterations; i++ { 961 LockBucket(&mu) 962 n := atomic.AddInt32(&activity, 1) 963 if n != 1 { 964 UnlockBucket(&mu) 965 panic(fmt.Sprintf("lock(%d)\n", n)) 966 } 967 atomic.AddInt32(&activity, -1) 968 UnlockBucket(&mu) 969 } 970 cdone <- true 971 }() 972 } 973 // Wait for all lockers to finish. 974 for i := 0; i < numLockers; i++ { 975 <-cdone 976 } 977 } 978 979 func TestMapTopHashMutex_Store_NoLock(t *testing.T) { 980 mu := uint64(0) 981 testMapTopHashMutex_Store(t, &mu) 982 } 983 984 func TestMapTopHashMutex_Store_WhileLocked(t *testing.T) { 985 mu := uint64(0) 986 LockBucket(&mu) 987 defer UnlockBucket(&mu) 988 testMapTopHashMutex_Store(t, &mu) 989 } 990 991 func testMapTopHashMutex_Store(t *testing.T, topHashes *uint64) { 992 hash := uint64(0b1101_0100_1010_1011_1101 << 44) 993 for i := 0; i < EntriesPerMapBucket; i++ { 994 if TopHashMatch(hash, *topHashes, i) { 995 t.Fatalf("top hash match for all zeros for index %d", i) 996 } 997 998 prevOnes := bits.OnesCount64(*topHashes) 999 *topHashes = StoreTopHash(hash, *topHashes, i) 1000 newOnes := bits.OnesCount64(*topHashes) 1001 expectedInc := bits.OnesCount64(hash) + 1 1002 if newOnes != prevOnes+expectedInc { 1003 t.Fatalf("unexpected bits change after store for index %d: %d, %d, %#b", 1004 i, newOnes, prevOnes+expectedInc, *topHashes) 1005 } 1006 1007 if !TopHashMatch(hash, *topHashes, i) { 1008 t.Fatalf("top hash mismatch after store for index %d: %#b", i, *topHashes) 1009 } 1010 } 1011 } 1012 1013 func TestMapTopHashMutex_Erase_NoLock(t *testing.T) { 1014 mu := uint64(0) 1015 testMapTopHashMutex_Erase(t, &mu) 1016 } 1017 1018 func TestMapTopHashMutex_Erase_WhileLocked(t *testing.T) { 1019 mu := uint64(0) 1020 LockBucket(&mu) 1021 defer UnlockBucket(&mu) 1022 testMapTopHashMutex_Erase(t, &mu) 1023 } 1024 1025 func testMapTopHashMutex_Erase(t *testing.T, topHashes *uint64) { 1026 hash := uint64(0xababaaaaaaaaaaaa) // top hash is 1010_1011_1010_1011_1010 1027 for i := 0; i < EntriesPerMapBucket; i++ { 1028 *topHashes = StoreTopHash(hash, *topHashes, i) 1029 ones := bits.OnesCount64(*topHashes) 1030 1031 *topHashes = EraseTopHash(*topHashes, i) 1032 if TopHashMatch(hash, *topHashes, i) { 1033 t.Fatalf("top hash match after erase for index %d: %#b", i, *topHashes) 1034 } 1035 1036 erasedBits := ones - bits.OnesCount64(*topHashes) 1037 if erasedBits != 1 { 1038 t.Fatalf("more than one bit changed after erase: %d, %d", i, erasedBits) 1039 } 1040 } 1041 } 1042 1043 func TestMapTopHashMutex_StoreAfterErase_NoLock(t *testing.T) { 1044 mu := uint64(0) 1045 testMapTopHashMutex_StoreAfterErase(t, &mu) 1046 } 1047 1048 func TestMapTopHashMutex_StoreAfterErase_WhileLocked(t *testing.T) { 1049 mu := uint64(0) 1050 LockBucket(&mu) 1051 defer UnlockBucket(&mu) 1052 testMapTopHashMutex_StoreAfterErase(t, &mu) 1053 } 1054 1055 func testMapTopHashMutex_StoreAfterErase(t *testing.T, topHashes *uint64) { 1056 hashOne := uint64(0b1101_0100_1101_0100_1101_1111 << 40) // top hash is 1101_0100_1101_0100_1101 1057 hashTwo := uint64(0b1010_1011_1010_1011_1010_1111 << 40) // top hash is 1010_1011_1010_1011_1010 1058 idx := 2 1059 1060 *topHashes = StoreTopHash(hashOne, *topHashes, idx) 1061 if !TopHashMatch(hashOne, *topHashes, idx) { 1062 t.Fatalf("top hash mismatch for hash one: %#b, %#b", hashOne, *topHashes) 1063 } 1064 if TopHashMatch(hashTwo, *topHashes, idx) { 1065 t.Fatalf("top hash match for hash two: %#b, %#b", hashTwo, *topHashes) 1066 } 1067 1068 *topHashes = EraseTopHash(*topHashes, idx) 1069 *topHashes = StoreTopHash(hashTwo, *topHashes, idx) 1070 if TopHashMatch(hashOne, *topHashes, idx) { 1071 t.Fatalf("top hash match for hash one: %#b, %#b", hashOne, *topHashes) 1072 } 1073 if !TopHashMatch(hashTwo, *topHashes, idx) { 1074 t.Fatalf("top hash mismatch for hash two: %#b, %#b", hashTwo, *topHashes) 1075 } 1076 } 1077 1078 func BenchmarkMap_NoWarmUp(b *testing.B) { 1079 for _, bc := range benchmarkCases { 1080 if bc.readPercentage == 100 { 1081 // This benchmark doesn't make sense without a warm-up. 1082 continue 1083 } 1084 b.Run(bc.name, func(b *testing.B) { 1085 m := NewMap() 1086 benchmarkMap(b, func(k string) (interface{}, bool) { 1087 return m.Load(k) 1088 }, func(k string, v interface{}) { 1089 m.Store(k, v) 1090 }, func(k string) { 1091 m.Delete(k) 1092 }, bc.readPercentage) 1093 }) 1094 } 1095 } 1096 1097 func BenchmarkMapStandard_NoWarmUp(b *testing.B) { 1098 for _, bc := range benchmarkCases { 1099 if bc.readPercentage == 100 { 1100 // This benchmark doesn't make sense without a warm-up. 1101 continue 1102 } 1103 b.Run(bc.name, func(b *testing.B) { 1104 var m sync.Map 1105 benchmarkMap(b, func(k string) (interface{}, bool) { 1106 return m.Load(k) 1107 }, func(k string, v interface{}) { 1108 m.Store(k, v) 1109 }, func(k string) { 1110 m.Delete(k) 1111 }, bc.readPercentage) 1112 }) 1113 } 1114 } 1115 1116 func BenchmarkMap_WarmUp(b *testing.B) { 1117 for _, bc := range benchmarkCases { 1118 b.Run(bc.name, func(b *testing.B) { 1119 m := NewMapPresized(benchmarkNumEntries) 1120 for i := 0; i < benchmarkNumEntries; i++ { 1121 m.Store(benchmarkKeyPrefix+strconv.Itoa(i), i) 1122 } 1123 b.ResetTimer() 1124 benchmarkMap(b, func(k string) (interface{}, bool) { 1125 return m.Load(k) 1126 }, func(k string, v interface{}) { 1127 m.Store(k, v) 1128 }, func(k string) { 1129 m.Delete(k) 1130 }, bc.readPercentage) 1131 }) 1132 } 1133 } 1134 1135 // This is a nice scenario for sync.Map since a lot of updates 1136 // will hit the readOnly part of the map. 1137 func BenchmarkMapStandard_WarmUp(b *testing.B) { 1138 for _, bc := range benchmarkCases { 1139 b.Run(bc.name, func(b *testing.B) { 1140 var m sync.Map 1141 for i := 0; i < benchmarkNumEntries; i++ { 1142 m.Store(benchmarkKeyPrefix+strconv.Itoa(i), i) 1143 } 1144 b.ResetTimer() 1145 benchmarkMap(b, func(k string) (interface{}, bool) { 1146 return m.Load(k) 1147 }, func(k string, v interface{}) { 1148 m.Store(k, v) 1149 }, func(k string) { 1150 m.Delete(k) 1151 }, bc.readPercentage) 1152 }) 1153 } 1154 } 1155 1156 func benchmarkMap( 1157 b *testing.B, 1158 loadFn func(k string) (interface{}, bool), 1159 storeFn func(k string, v interface{}), 1160 deleteFn func(k string), 1161 readPercentage int, 1162 ) { 1163 runParallel(b, func(pb *testing.PB) { 1164 // convert percent to permille to support 99% case 1165 storeThreshold := 10 * readPercentage 1166 deleteThreshold := 10*readPercentage + ((1000 - 10*readPercentage) / 2) 1167 for pb.Next() { 1168 op := int(Fastrand() % 1000) 1169 i := int(Fastrand() % benchmarkNumEntries) 1170 if op >= deleteThreshold { 1171 deleteFn(benchmarkKeys[i]) 1172 } else if op >= storeThreshold { 1173 storeFn(benchmarkKeys[i], i) 1174 } else { 1175 loadFn(benchmarkKeys[i]) 1176 } 1177 } 1178 }) 1179 } 1180 1181 func BenchmarkMapRange(b *testing.B) { 1182 m := NewMap() 1183 for i := 0; i < benchmarkNumEntries; i++ { 1184 m.Store(benchmarkKeys[i], i) 1185 } 1186 b.ResetTimer() 1187 runParallel(b, func(pb *testing.PB) { 1188 foo := 0 1189 for pb.Next() { 1190 m.Range(func(key string, value interface{}) bool { 1191 // Dereference the value to have an apple-to-apple 1192 // comparison with MapOf.Range. 1193 _ = value.(int) 1194 foo++ 1195 return true 1196 }) 1197 _ = foo 1198 } 1199 }) 1200 } 1201 1202 func BenchmarkMapRangeStandard(b *testing.B) { 1203 var m sync.Map 1204 for i := 0; i < benchmarkNumEntries; i++ { 1205 m.Store(benchmarkKeys[i], i) 1206 } 1207 b.ResetTimer() 1208 runParallel(b, func(pb *testing.PB) { 1209 foo := 0 1210 for pb.Next() { 1211 m.Range(func(key interface{}, value interface{}) bool { 1212 // Dereference the key and the value to have an apple-to-apple 1213 // comparison with MapOf.Range. 1214 _, _ = key.(string), value.(int) 1215 foo++ 1216 return true 1217 }) 1218 _ = foo 1219 } 1220 }) 1221 }