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