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