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