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