github.com/puzpuzpuz/xsync/v3@v3.1.1-0.20240225193106-cbe4ec1e954f/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/v3" 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 TestMapStoreThenParallelDelete_DoesNotShrinkBelowMinTableLen(t *testing.T) { 474 const numEntries = 1000 475 m := NewMap() 476 for i := 0; i < numEntries; i++ { 477 m.Store(strconv.Itoa(i), i) 478 } 479 480 cdone := make(chan bool) 481 go func() { 482 for i := 0; i < numEntries; i++ { 483 m.Delete(strconv.Itoa(int(i))) 484 } 485 cdone <- true 486 }() 487 go func() { 488 for i := 0; i < numEntries; i++ { 489 m.Delete(strconv.Itoa(int(i))) 490 } 491 cdone <- true 492 }() 493 494 // Wait for the goroutines to finish. 495 <-cdone 496 <-cdone 497 498 stats := CollectMapStats(m) 499 if stats.RootBuckets != DefaultMinMapTableLen { 500 t.Fatalf("table length was different from the minimum: %d", stats.RootBuckets) 501 } 502 } 503 504 func sizeBasedOnRange(m *Map) int { 505 size := 0 506 m.Range(func(key string, value interface{}) bool { 507 size++ 508 return true 509 }) 510 return size 511 } 512 513 func TestMapSize(t *testing.T) { 514 const numEntries = 1000 515 m := NewMap() 516 size := m.Size() 517 if size != 0 { 518 t.Fatalf("zero size expected: %d", size) 519 } 520 expectedSize := 0 521 for i := 0; i < numEntries; i++ { 522 m.Store(strconv.Itoa(i), i) 523 expectedSize++ 524 size := m.Size() 525 if size != expectedSize { 526 t.Fatalf("size of %d was expected, got: %d", expectedSize, size) 527 } 528 rsize := sizeBasedOnRange(m) 529 if size != rsize { 530 t.Fatalf("size does not match number of entries in Range: %v, %v", size, rsize) 531 } 532 } 533 for i := 0; i < numEntries; i++ { 534 m.Delete(strconv.Itoa(i)) 535 expectedSize-- 536 size := m.Size() 537 if size != expectedSize { 538 t.Fatalf("size of %d was expected, got: %d", expectedSize, size) 539 } 540 rsize := sizeBasedOnRange(m) 541 if size != rsize { 542 t.Fatalf("size does not match number of entries in Range: %v, %v", size, rsize) 543 } 544 } 545 } 546 547 func TestMapClear(t *testing.T) { 548 const numEntries = 1000 549 m := NewMap() 550 for i := 0; i < numEntries; i++ { 551 m.Store(strconv.Itoa(i), i) 552 } 553 size := m.Size() 554 if size != numEntries { 555 t.Fatalf("size of %d was expected, got: %d", numEntries, size) 556 } 557 m.Clear() 558 size = m.Size() 559 if size != 0 { 560 t.Fatalf("zero size was expected, got: %d", size) 561 } 562 rsize := sizeBasedOnRange(m) 563 if rsize != 0 { 564 t.Fatalf("zero number of entries in Range was expected, got: %d", rsize) 565 } 566 } 567 568 func assertMapCapacity(t *testing.T, m *Map, expectedCap int) { 569 stats := CollectMapStats(m) 570 if stats.Capacity != expectedCap { 571 t.Fatalf("capacity was different from %d: %d", expectedCap, stats.Capacity) 572 } 573 } 574 575 func TestNewMapPresized(t *testing.T) { 576 assertMapCapacity(t, NewMap(), DefaultMinMapTableCap) 577 assertMapCapacity(t, NewMapPresized(1000), 1536) 578 assertMapCapacity(t, NewMapPresized(0), DefaultMinMapTableCap) 579 assertMapCapacity(t, NewMapPresized(-1), DefaultMinMapTableCap) 580 } 581 582 func TestNewMapPresized_DoesNotShrinkBelowMinTableLen(t *testing.T) { 583 const minTableLen = 1024 584 const numEntries = minTableLen * EntriesPerMapBucket 585 m := NewMapPresized(numEntries) 586 for i := 0; i < numEntries; i++ { 587 m.Store(strconv.Itoa(i), i) 588 } 589 590 stats := CollectMapStats(m) 591 if stats.RootBuckets <= minTableLen { 592 t.Fatalf("table did not grow: %d", stats.RootBuckets) 593 } 594 595 for i := 0; i < numEntries; i++ { 596 m.Delete(strconv.Itoa(int(i))) 597 } 598 599 stats = CollectMapStats(m) 600 if stats.RootBuckets != minTableLen { 601 t.Fatalf("table length was different from the minimum: %d", stats.RootBuckets) 602 } 603 } 604 605 func TestMapResize(t *testing.T) { 606 const numEntries = 100_000 607 m := NewMap() 608 609 for i := 0; i < numEntries; i++ { 610 m.Store(strconv.Itoa(i), i) 611 } 612 stats := CollectMapStats(m) 613 if stats.Size != numEntries { 614 t.Fatalf("size was too small: %d", stats.Size) 615 } 616 expectedCapacity := int(math.RoundToEven(MapLoadFactor+1)) * stats.RootBuckets * EntriesPerMapBucket 617 if stats.Capacity > expectedCapacity { 618 t.Fatalf("capacity was too large: %d, expected: %d", stats.Capacity, expectedCapacity) 619 } 620 if stats.RootBuckets <= DefaultMinMapTableLen { 621 t.Fatalf("table was too small: %d", stats.RootBuckets) 622 } 623 if stats.TotalGrowths == 0 { 624 t.Fatalf("non-zero total growths expected: %d", stats.TotalGrowths) 625 } 626 if stats.TotalShrinks > 0 { 627 t.Fatalf("zero total shrinks expected: %d", stats.TotalShrinks) 628 } 629 // This is useful when debugging table resize and occupancy. 630 // Use -v flag to see the output. 631 t.Log(stats.ToString()) 632 633 for i := 0; i < numEntries; i++ { 634 m.Delete(strconv.Itoa(i)) 635 } 636 stats = CollectMapStats(m) 637 if stats.Size > 0 { 638 t.Fatalf("zero size was expected: %d", stats.Size) 639 } 640 expectedCapacity = stats.RootBuckets * EntriesPerMapBucket 641 if stats.Capacity != expectedCapacity { 642 t.Fatalf("capacity was too large: %d, expected: %d", stats.Capacity, expectedCapacity) 643 } 644 if stats.RootBuckets != DefaultMinMapTableLen { 645 t.Fatalf("table was too large: %d", stats.RootBuckets) 646 } 647 if stats.TotalShrinks == 0 { 648 t.Fatalf("non-zero total shrinks expected: %d", stats.TotalShrinks) 649 } 650 t.Log(stats.ToString()) 651 } 652 653 func TestMapResize_CounterLenLimit(t *testing.T) { 654 const numEntries = 1_000_000 655 m := NewMap() 656 657 for i := 0; i < numEntries; i++ { 658 m.Store("foo"+strconv.Itoa(i), "bar"+strconv.Itoa(i)) 659 } 660 stats := CollectMapStats(m) 661 if stats.Size != numEntries { 662 t.Fatalf("size was too small: %d", stats.Size) 663 } 664 if stats.CounterLen != MaxMapCounterLen { 665 t.Fatalf("number of counter stripes was too large: %d, expected: %d", 666 stats.CounterLen, MaxMapCounterLen) 667 } 668 } 669 670 func parallelSeqResizer(t *testing.T, m *Map, numEntries int, positive bool, cdone chan bool) { 671 for i := 0; i < numEntries; i++ { 672 if positive { 673 m.Store(strconv.Itoa(i), i) 674 } else { 675 m.Store(strconv.Itoa(-i), -i) 676 } 677 } 678 cdone <- true 679 } 680 681 func TestMapParallelResize_GrowOnly(t *testing.T) { 682 const numEntries = 100_000 683 m := NewMap() 684 cdone := make(chan bool) 685 go parallelSeqResizer(t, m, numEntries, true, cdone) 686 go parallelSeqResizer(t, m, numEntries, false, cdone) 687 // Wait for the goroutines to finish. 688 <-cdone 689 <-cdone 690 // Verify map contents. 691 for i := -numEntries + 1; i < numEntries; i++ { 692 v, ok := m.Load(strconv.Itoa(i)) 693 if !ok { 694 t.Fatalf("value not found for %d", i) 695 } 696 if vi, ok := v.(int); ok && vi != i { 697 t.Fatalf("values do not match for %d: %v", i, v) 698 } 699 } 700 if s := m.Size(); s != 2*numEntries-1 { 701 t.Fatalf("unexpected size: %v", s) 702 } 703 } 704 705 func parallelRandResizer(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 706 r := rand.New(rand.NewSource(time.Now().UnixNano())) 707 for i := 0; i < numIters; i++ { 708 coin := r.Int63n(2) 709 for j := 0; j < numEntries; j++ { 710 if coin == 1 { 711 m.Store(strconv.Itoa(j), j) 712 } else { 713 m.Delete(strconv.Itoa(j)) 714 } 715 } 716 } 717 cdone <- true 718 } 719 720 func TestMapParallelResize(t *testing.T) { 721 const numIters = 1_000 722 const numEntries = 2 * EntriesPerMapBucket * DefaultMinMapTableLen 723 m := NewMap() 724 cdone := make(chan bool) 725 go parallelRandResizer(t, m, numIters, numEntries, cdone) 726 go parallelRandResizer(t, m, numIters, numEntries, cdone) 727 // Wait for the goroutines to finish. 728 <-cdone 729 <-cdone 730 // Verify map contents. 731 for i := 0; i < numEntries; i++ { 732 v, ok := m.Load(strconv.Itoa(i)) 733 if !ok { 734 // The entry may be deleted and that's ok. 735 continue 736 } 737 if vi, ok := v.(int); ok && vi != i { 738 t.Fatalf("values do not match for %d: %v", i, v) 739 } 740 } 741 s := m.Size() 742 if s > numEntries { 743 t.Fatalf("unexpected size: %v", s) 744 } 745 rs := sizeBasedOnRange(m) 746 if s != rs { 747 t.Fatalf("size does not match number of entries in Range: %v, %v", s, rs) 748 } 749 } 750 751 func parallelRandClearer(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 752 r := rand.New(rand.NewSource(time.Now().UnixNano())) 753 for i := 0; i < numIters; i++ { 754 coin := r.Int63n(2) 755 for j := 0; j < numEntries; j++ { 756 if coin == 1 { 757 m.Store(strconv.Itoa(j), j) 758 } else { 759 m.Clear() 760 } 761 } 762 } 763 cdone <- true 764 } 765 766 func TestMapParallelClear(t *testing.T) { 767 const numIters = 100 768 const numEntries = 1_000 769 m := NewMap() 770 cdone := make(chan bool) 771 go parallelRandClearer(t, m, numIters, numEntries, cdone) 772 go parallelRandClearer(t, m, numIters, numEntries, cdone) 773 // Wait for the goroutines to finish. 774 <-cdone 775 <-cdone 776 // Verify map size. 777 s := m.Size() 778 if s > numEntries { 779 t.Fatalf("unexpected size: %v", s) 780 } 781 rs := sizeBasedOnRange(m) 782 if s != rs { 783 t.Fatalf("size does not match number of entries in Range: %v, %v", s, rs) 784 } 785 } 786 787 func parallelSeqStorer(t *testing.T, m *Map, storeEach, numIters, numEntries int, cdone chan bool) { 788 for i := 0; i < numIters; i++ { 789 for j := 0; j < numEntries; j++ { 790 if storeEach == 0 || j%storeEach == 0 { 791 m.Store(strconv.Itoa(j), j) 792 // Due to atomic snapshots we must see a "<j>"/j pair. 793 v, ok := m.Load(strconv.Itoa(j)) 794 if !ok { 795 t.Errorf("value was not found for %d", j) 796 break 797 } 798 if vi, ok := v.(int); !ok || vi != j { 799 t.Errorf("value was not expected for %d: %d", j, vi) 800 break 801 } 802 } 803 } 804 } 805 cdone <- true 806 } 807 808 func TestMapParallelStores(t *testing.T) { 809 const numStorers = 4 810 const numIters = 10_000 811 const numEntries = 100 812 m := NewMap() 813 cdone := make(chan bool) 814 for i := 0; i < numStorers; i++ { 815 go parallelSeqStorer(t, m, i, numIters, numEntries, cdone) 816 } 817 // Wait for the goroutines to finish. 818 for i := 0; i < numStorers; i++ { 819 <-cdone 820 } 821 // Verify map contents. 822 for i := 0; i < numEntries; i++ { 823 v, ok := m.Load(strconv.Itoa(i)) 824 if !ok { 825 t.Fatalf("value not found for %d", i) 826 } 827 if vi, ok := v.(int); ok && vi != i { 828 t.Fatalf("values do not match for %d: %v", i, v) 829 } 830 } 831 } 832 833 func parallelRandStorer(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 834 r := rand.New(rand.NewSource(time.Now().UnixNano())) 835 for i := 0; i < numIters; i++ { 836 j := r.Intn(numEntries) 837 if v, loaded := m.LoadOrStore(strconv.Itoa(j), j); loaded { 838 if vi, ok := v.(int); !ok || vi != j { 839 t.Errorf("value was not expected for %d: %d", j, vi) 840 } 841 } 842 } 843 cdone <- true 844 } 845 846 func parallelRandDeleter(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 847 r := rand.New(rand.NewSource(time.Now().UnixNano())) 848 for i := 0; i < numIters; i++ { 849 j := r.Intn(numEntries) 850 if v, loaded := m.LoadAndDelete(strconv.Itoa(j)); loaded { 851 if vi, ok := v.(int); !ok || vi != j { 852 t.Errorf("value was not expected for %d: %d", j, vi) 853 } 854 } 855 } 856 cdone <- true 857 } 858 859 func parallelLoader(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 860 for i := 0; i < numIters; i++ { 861 for j := 0; j < numEntries; j++ { 862 // Due to atomic snapshots we must either see no entry, or a "<j>"/j pair. 863 if v, ok := m.Load(strconv.Itoa(j)); ok { 864 if vi, ok := v.(int); !ok || vi != j { 865 t.Errorf("value was not expected for %d: %d", j, vi) 866 } 867 } 868 } 869 } 870 cdone <- true 871 } 872 873 func TestMapAtomicSnapshot(t *testing.T) { 874 const numIters = 100_000 875 const numEntries = 100 876 m := NewMap() 877 cdone := make(chan bool) 878 // Update or delete random entry in parallel with loads. 879 go parallelRandStorer(t, m, numIters, numEntries, cdone) 880 go parallelRandDeleter(t, m, numIters, numEntries, cdone) 881 go parallelLoader(t, m, numIters, numEntries, cdone) 882 // Wait for the goroutines to finish. 883 for i := 0; i < 3; i++ { 884 <-cdone 885 } 886 } 887 888 func TestMapParallelStoresAndDeletes(t *testing.T) { 889 const numWorkers = 2 890 const numIters = 100_000 891 const numEntries = 1000 892 m := NewMap() 893 cdone := make(chan bool) 894 // Update random entry in parallel with deletes. 895 for i := 0; i < numWorkers; i++ { 896 go parallelRandStorer(t, m, numIters, numEntries, cdone) 897 go parallelRandDeleter(t, m, numIters, numEntries, cdone) 898 } 899 // Wait for the goroutines to finish. 900 for i := 0; i < 2*numWorkers; i++ { 901 <-cdone 902 } 903 } 904 905 func parallelComputer(t *testing.T, m *Map, numIters, numEntries int, cdone chan bool) { 906 for i := 0; i < numIters; i++ { 907 for j := 0; j < numEntries; j++ { 908 m.Compute(strconv.Itoa(j), func(oldValue interface{}, loaded bool) (newValue interface{}, delete bool) { 909 if !loaded { 910 return uint64(1), false 911 } 912 return uint64(oldValue.(uint64) + 1), false 913 }) 914 } 915 } 916 cdone <- true 917 } 918 919 func TestMapParallelComputes(t *testing.T) { 920 const numWorkers = 4 // Also stands for numEntries. 921 const numIters = 10_000 922 m := NewMap() 923 cdone := make(chan bool) 924 for i := 0; i < numWorkers; i++ { 925 go parallelComputer(t, m, numIters, numWorkers, cdone) 926 } 927 // Wait for the goroutines to finish. 928 for i := 0; i < numWorkers; i++ { 929 <-cdone 930 } 931 // Verify map contents. 932 for i := 0; i < numWorkers; i++ { 933 v, ok := m.Load(strconv.Itoa(i)) 934 if !ok { 935 t.Fatalf("value not found for %d", i) 936 } 937 if v.(uint64) != numWorkers*numIters { 938 t.Fatalf("values do not match for %d: %v", i, v) 939 } 940 } 941 } 942 943 func parallelRangeStorer(t *testing.T, m *Map, numEntries int, stopFlag *int64, cdone chan bool) { 944 for { 945 for i := 0; i < numEntries; i++ { 946 m.Store(strconv.Itoa(i), i) 947 } 948 if atomic.LoadInt64(stopFlag) != 0 { 949 break 950 } 951 } 952 cdone <- true 953 } 954 955 func parallelRangeDeleter(t *testing.T, m *Map, numEntries int, stopFlag *int64, cdone chan bool) { 956 for { 957 for i := 0; i < numEntries; i++ { 958 m.Delete(strconv.Itoa(i)) 959 } 960 if atomic.LoadInt64(stopFlag) != 0 { 961 break 962 } 963 } 964 cdone <- true 965 } 966 967 func TestMapParallelRange(t *testing.T) { 968 const numEntries = 10_000 969 m := NewMap() 970 for i := 0; i < numEntries; i++ { 971 m.Store(strconv.Itoa(i), i) 972 } 973 // Start goroutines that would be storing and deleting items in parallel. 974 cdone := make(chan bool) 975 stopFlag := int64(0) 976 go parallelRangeStorer(t, m, numEntries, &stopFlag, cdone) 977 go parallelRangeDeleter(t, m, numEntries, &stopFlag, cdone) 978 // Iterate the map and verify that no duplicate keys were met. 979 met := make(map[string]int) 980 m.Range(func(key string, value interface{}) bool { 981 if key != strconv.Itoa(value.(int)) { 982 t.Fatalf("got unexpected value for key %s: %v", key, value) 983 return false 984 } 985 met[key] += 1 986 return true 987 }) 988 if len(met) == 0 { 989 t.Fatal("no entries were met when iterating") 990 } 991 for k, c := range met { 992 if c != 1 { 993 t.Fatalf("met key %s multiple times: %d", k, c) 994 } 995 } 996 // Make sure that both goroutines finish. 997 atomic.StoreInt64(&stopFlag, 1) 998 <-cdone 999 <-cdone 1000 } 1001 1002 func parallelShrinker(t *testing.T, m *Map, numIters, numEntries int, stopFlag *int64, cdone chan bool) { 1003 for i := 0; i < numIters; i++ { 1004 for j := 0; j < numEntries; j++ { 1005 if p, loaded := m.LoadOrStore(strconv.Itoa(j), &point{int32(j), int32(j)}); loaded { 1006 t.Errorf("value was present for %d: %v", j, p) 1007 } 1008 } 1009 for j := 0; j < numEntries; j++ { 1010 m.Delete(strconv.Itoa(j)) 1011 } 1012 } 1013 atomic.StoreInt64(stopFlag, 1) 1014 cdone <- true 1015 } 1016 1017 func parallelUpdater(t *testing.T, m *Map, idx int, stopFlag *int64, cdone chan bool) { 1018 for atomic.LoadInt64(stopFlag) != 1 { 1019 sleepUs := int(Fastrand() % 10) 1020 if p, loaded := m.LoadOrStore(strconv.Itoa(idx), &point{int32(idx), int32(idx)}); loaded { 1021 t.Errorf("value was present for %d: %v", idx, p) 1022 } 1023 time.Sleep(time.Duration(sleepUs) * time.Microsecond) 1024 if _, ok := m.Load(strconv.Itoa(idx)); !ok { 1025 t.Errorf("value was not found for %d", idx) 1026 } 1027 m.Delete(strconv.Itoa(idx)) 1028 } 1029 cdone <- true 1030 } 1031 1032 func TestMapDoesNotLoseEntriesOnResize(t *testing.T) { 1033 const numIters = 10_000 1034 const numEntries = 128 1035 m := NewMap() 1036 cdone := make(chan bool) 1037 stopFlag := int64(0) 1038 go parallelShrinker(t, m, numIters, numEntries, &stopFlag, cdone) 1039 go parallelUpdater(t, m, numEntries, &stopFlag, cdone) 1040 // Wait for the goroutines to finish. 1041 <-cdone 1042 <-cdone 1043 // Verify map contents. 1044 if s := m.Size(); s != 0 { 1045 t.Fatalf("map is not empty: %d", s) 1046 } 1047 } 1048 1049 func TestMapTopHashMutex(t *testing.T) { 1050 const ( 1051 numLockers = 4 1052 numIterations = 1000 1053 ) 1054 var ( 1055 activity int32 1056 mu uint64 1057 ) 1058 cdone := make(chan bool) 1059 for i := 0; i < numLockers; i++ { 1060 go func() { 1061 for i := 0; i < numIterations; i++ { 1062 LockBucket(&mu) 1063 n := atomic.AddInt32(&activity, 1) 1064 if n != 1 { 1065 UnlockBucket(&mu) 1066 panic(fmt.Sprintf("lock(%d)\n", n)) 1067 } 1068 atomic.AddInt32(&activity, -1) 1069 UnlockBucket(&mu) 1070 } 1071 cdone <- true 1072 }() 1073 } 1074 // Wait for all lockers to finish. 1075 for i := 0; i < numLockers; i++ { 1076 <-cdone 1077 } 1078 } 1079 1080 func TestMapTopHashMutex_Store_NoLock(t *testing.T) { 1081 mu := uint64(0) 1082 testMapTopHashMutex_Store(t, &mu) 1083 } 1084 1085 func TestMapTopHashMutex_Store_WhileLocked(t *testing.T) { 1086 mu := uint64(0) 1087 LockBucket(&mu) 1088 defer UnlockBucket(&mu) 1089 testMapTopHashMutex_Store(t, &mu) 1090 } 1091 1092 func testMapTopHashMutex_Store(t *testing.T, topHashes *uint64) { 1093 hash := uint64(0b1101_0100_1010_1011_1101 << 44) 1094 for i := 0; i < EntriesPerMapBucket; i++ { 1095 if TopHashMatch(hash, *topHashes, i) { 1096 t.Fatalf("top hash match for all zeros for index %d", i) 1097 } 1098 1099 prevOnes := bits.OnesCount64(*topHashes) 1100 *topHashes = StoreTopHash(hash, *topHashes, i) 1101 newOnes := bits.OnesCount64(*topHashes) 1102 expectedInc := bits.OnesCount64(hash) + 1 1103 if newOnes != prevOnes+expectedInc { 1104 t.Fatalf("unexpected bits change after store for index %d: %d, %d, %#b", 1105 i, newOnes, prevOnes+expectedInc, *topHashes) 1106 } 1107 1108 if !TopHashMatch(hash, *topHashes, i) { 1109 t.Fatalf("top hash mismatch after store for index %d: %#b", i, *topHashes) 1110 } 1111 } 1112 } 1113 1114 func TestMapTopHashMutex_Erase_NoLock(t *testing.T) { 1115 mu := uint64(0) 1116 testMapTopHashMutex_Erase(t, &mu) 1117 } 1118 1119 func TestMapTopHashMutex_Erase_WhileLocked(t *testing.T) { 1120 mu := uint64(0) 1121 LockBucket(&mu) 1122 defer UnlockBucket(&mu) 1123 testMapTopHashMutex_Erase(t, &mu) 1124 } 1125 1126 func testMapTopHashMutex_Erase(t *testing.T, topHashes *uint64) { 1127 hash := uint64(0xababaaaaaaaaaaaa) // top hash is 1010_1011_1010_1011_1010 1128 for i := 0; i < EntriesPerMapBucket; i++ { 1129 *topHashes = StoreTopHash(hash, *topHashes, i) 1130 ones := bits.OnesCount64(*topHashes) 1131 1132 *topHashes = EraseTopHash(*topHashes, i) 1133 if TopHashMatch(hash, *topHashes, i) { 1134 t.Fatalf("top hash match after erase for index %d: %#b", i, *topHashes) 1135 } 1136 1137 erasedBits := ones - bits.OnesCount64(*topHashes) 1138 if erasedBits != 1 { 1139 t.Fatalf("more than one bit changed after erase: %d, %d", i, erasedBits) 1140 } 1141 } 1142 } 1143 1144 func TestMapTopHashMutex_StoreAfterErase_NoLock(t *testing.T) { 1145 mu := uint64(0) 1146 testMapTopHashMutex_StoreAfterErase(t, &mu) 1147 } 1148 1149 func TestMapTopHashMutex_StoreAfterErase_WhileLocked(t *testing.T) { 1150 mu := uint64(0) 1151 LockBucket(&mu) 1152 defer UnlockBucket(&mu) 1153 testMapTopHashMutex_StoreAfterErase(t, &mu) 1154 } 1155 1156 func testMapTopHashMutex_StoreAfterErase(t *testing.T, topHashes *uint64) { 1157 hashOne := uint64(0b1101_0100_1101_0100_1101_1111 << 40) // top hash is 1101_0100_1101_0100_1101 1158 hashTwo := uint64(0b1010_1011_1010_1011_1010_1111 << 40) // top hash is 1010_1011_1010_1011_1010 1159 idx := 2 1160 1161 *topHashes = StoreTopHash(hashOne, *topHashes, idx) 1162 if !TopHashMatch(hashOne, *topHashes, idx) { 1163 t.Fatalf("top hash mismatch for hash one: %#b, %#b", hashOne, *topHashes) 1164 } 1165 if TopHashMatch(hashTwo, *topHashes, idx) { 1166 t.Fatalf("top hash match for hash two: %#b, %#b", hashTwo, *topHashes) 1167 } 1168 1169 *topHashes = EraseTopHash(*topHashes, idx) 1170 *topHashes = StoreTopHash(hashTwo, *topHashes, idx) 1171 if TopHashMatch(hashOne, *topHashes, idx) { 1172 t.Fatalf("top hash match for hash one: %#b, %#b", hashOne, *topHashes) 1173 } 1174 if !TopHashMatch(hashTwo, *topHashes, idx) { 1175 t.Fatalf("top hash mismatch for hash two: %#b, %#b", hashTwo, *topHashes) 1176 } 1177 } 1178 1179 func BenchmarkMap_NoWarmUp(b *testing.B) { 1180 for _, bc := range benchmarkCases { 1181 if bc.readPercentage == 100 { 1182 // This benchmark doesn't make sense without a warm-up. 1183 continue 1184 } 1185 b.Run(bc.name, func(b *testing.B) { 1186 m := NewMap() 1187 benchmarkMap(b, func(k string) (interface{}, bool) { 1188 return m.Load(k) 1189 }, func(k string, v interface{}) { 1190 m.Store(k, v) 1191 }, func(k string) { 1192 m.Delete(k) 1193 }, bc.readPercentage) 1194 }) 1195 } 1196 } 1197 1198 func BenchmarkMapStandard_NoWarmUp(b *testing.B) { 1199 for _, bc := range benchmarkCases { 1200 if bc.readPercentage == 100 { 1201 // This benchmark doesn't make sense without a warm-up. 1202 continue 1203 } 1204 b.Run(bc.name, func(b *testing.B) { 1205 var m sync.Map 1206 benchmarkMap(b, func(k string) (interface{}, bool) { 1207 return m.Load(k) 1208 }, func(k string, v interface{}) { 1209 m.Store(k, v) 1210 }, func(k string) { 1211 m.Delete(k) 1212 }, bc.readPercentage) 1213 }) 1214 } 1215 } 1216 1217 func BenchmarkMap_WarmUp(b *testing.B) { 1218 for _, bc := range benchmarkCases { 1219 b.Run(bc.name, func(b *testing.B) { 1220 m := NewMapPresized(benchmarkNumEntries) 1221 for i := 0; i < benchmarkNumEntries; i++ { 1222 m.Store(benchmarkKeyPrefix+strconv.Itoa(i), i) 1223 } 1224 b.ResetTimer() 1225 benchmarkMap(b, func(k string) (interface{}, bool) { 1226 return m.Load(k) 1227 }, func(k string, v interface{}) { 1228 m.Store(k, v) 1229 }, func(k string) { 1230 m.Delete(k) 1231 }, bc.readPercentage) 1232 }) 1233 } 1234 } 1235 1236 // This is a nice scenario for sync.Map since a lot of updates 1237 // will hit the readOnly part of the map. 1238 func BenchmarkMapStandard_WarmUp(b *testing.B) { 1239 for _, bc := range benchmarkCases { 1240 b.Run(bc.name, func(b *testing.B) { 1241 var m sync.Map 1242 for i := 0; i < benchmarkNumEntries; i++ { 1243 m.Store(benchmarkKeyPrefix+strconv.Itoa(i), i) 1244 } 1245 b.ResetTimer() 1246 benchmarkMap(b, func(k string) (interface{}, bool) { 1247 return m.Load(k) 1248 }, func(k string, v interface{}) { 1249 m.Store(k, v) 1250 }, func(k string) { 1251 m.Delete(k) 1252 }, bc.readPercentage) 1253 }) 1254 } 1255 } 1256 1257 func benchmarkMap( 1258 b *testing.B, 1259 loadFn func(k string) (interface{}, bool), 1260 storeFn func(k string, v interface{}), 1261 deleteFn func(k string), 1262 readPercentage int, 1263 ) { 1264 runParallel(b, func(pb *testing.PB) { 1265 // convert percent to permille to support 99% case 1266 storeThreshold := 10 * readPercentage 1267 deleteThreshold := 10*readPercentage + ((1000 - 10*readPercentage) / 2) 1268 for pb.Next() { 1269 op := int(Fastrand() % 1000) 1270 i := int(Fastrand() % benchmarkNumEntries) 1271 if op >= deleteThreshold { 1272 deleteFn(benchmarkKeys[i]) 1273 } else if op >= storeThreshold { 1274 storeFn(benchmarkKeys[i], i) 1275 } else { 1276 loadFn(benchmarkKeys[i]) 1277 } 1278 } 1279 }) 1280 } 1281 1282 func BenchmarkMapRange(b *testing.B) { 1283 m := NewMap() 1284 for i := 0; i < benchmarkNumEntries; i++ { 1285 m.Store(benchmarkKeys[i], i) 1286 } 1287 b.ResetTimer() 1288 runParallel(b, func(pb *testing.PB) { 1289 foo := 0 1290 for pb.Next() { 1291 m.Range(func(key string, value interface{}) bool { 1292 // Dereference the value to have an apple-to-apple 1293 // comparison with MapOf.Range. 1294 _ = value.(int) 1295 foo++ 1296 return true 1297 }) 1298 _ = foo 1299 } 1300 }) 1301 } 1302 1303 func BenchmarkMapRangeStandard(b *testing.B) { 1304 var m sync.Map 1305 for i := 0; i < benchmarkNumEntries; i++ { 1306 m.Store(benchmarkKeys[i], i) 1307 } 1308 b.ResetTimer() 1309 runParallel(b, func(pb *testing.PB) { 1310 foo := 0 1311 for pb.Next() { 1312 m.Range(func(key interface{}, value interface{}) bool { 1313 // Dereference the key and the value to have an apple-to-apple 1314 // comparison with MapOf.Range. 1315 _, _ = key.(string), value.(int) 1316 foo++ 1317 return true 1318 }) 1319 _ = foo 1320 } 1321 }) 1322 }