github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/kv/swiss_map_test.go (about) 1 package kv 2 3 import ( 4 "math/bits" 5 "math/rand" 6 randv2 "math/rand/v2" 7 "strconv" 8 "sync/atomic" 9 "testing" 10 11 "github.com/benz9527/xboot/lib/id" 12 "github.com/benz9527/xboot/lib/infra" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func fast16WayHashMatchInNonAMD64(md *[16]int8, hash int8) uint16 { 18 res := uint16(0) 19 for i := 0; i < 16; i++ { 20 if md[i] == hash { 21 res |= 1 << uint(i) 22 } 23 } 24 return res 25 } 26 27 func TestFast16WayHashMatchInNonAMD64(t *testing.T) { 28 md := new(swissMapMetadata) 29 hash := int8(0x51) 30 for i := 0; i < 16; i++ { 31 md[i] = empty 32 } 33 md[2] = hash 34 md[9] = hash 35 require.Equal(t, uint16(0x0204), fast16WayHashMatchInNonAMD64((*[slotSize]int8)(md), hash)) 36 require.Equal(t, uint16(0xFDFB), fast16WayHashMatchInNonAMD64((*[slotSize]int8)(md), empty)) 37 } 38 39 func TestTrailingZeros16(t *testing.T) { 40 bitset := uint16(0x0001) 41 for i := 0; i < 16; i++ { 42 tmp := bitset << i 43 require.Equal(t, i, bits.TrailingZeros16(tmp)) 44 } 45 } 46 47 func TestNextIndexInSlot(t *testing.T) { 48 bs := bitset(0x03) 49 nextIndexInSlot(&bs) 50 require.Equal(t, uint16(2), uint16(bs)) 51 52 bs = bitset(0x8F) 53 nextIndexInSlot(&bs) 54 require.Equal(t, uint16(0x8E), uint16(bs)) 55 56 bs = bitset(0x40) 57 nextIndexInSlot(&bs) 58 require.Equal(t, uint16(0), uint16(bs)) 59 } 60 61 func TestModN(t *testing.T) { 62 x := uint32(100) 63 n := uint32(50) 64 tmp := uint64(x) * uint64(n) 65 require.NotEqual(t, uint32(n-1)&x, uint32(tmp>>32)) 66 } 67 68 func genStrKeys(strLen, count int) (keys []string) { 69 nanoID, err := id.ClassicNanoID(strLen) 70 if err != nil { 71 panic(err) 72 } 73 keys = make([]string, count) 74 for i := range keys { 75 keys[i] = nanoID() 76 } 77 return 78 } 79 80 func genUint64Keys(count int) (keys []uint64) { 81 keys = make([]uint64, count) 82 var x uint64 83 for i := range keys { 84 x += (randv2.Uint64() % 128) + 1 85 keys[i] = x 86 } 87 return 88 } 89 90 func genFloat64Keys(count int) (keys []float64) { 91 keys = make([]float64, count) 92 var x float64 93 for i := range keys { 94 x += randv2.Float64() + 101.25 95 keys[i] = x 96 } 97 return 98 } 99 100 func uniqueKeys[K infra.OrderedKey](keys []K) []K { 101 s := make(map[K]struct{}, len(keys)) 102 for _, k := range keys { 103 s[k] = struct{}{} 104 } 105 u := make([]K, 0, len(keys)) 106 for k := range s { 107 u = append(u, k) 108 } 109 return u 110 } 111 112 func testSwissMapPutRunCore[K infra.OrderedKey](t *testing.T, keys []K) { 113 m := newSwissMap[K, int](uint32(len(keys))) 114 assert.Equal(t, int64(0), m.Len()) 115 for i, key := range keys { 116 m.Put(key, i) 117 } 118 assert.Equal(t, int64(len(keys)), m.Len()) 119 // overwrite 120 for i, key := range keys { 121 m.Put(key, -i) 122 } 123 assert.Equal(t, int64(len(keys)), m.Len()) 124 for i, key := range keys { 125 act, ok := m.Get(key) 126 assert.True(t, ok) 127 assert.Equal(t, -i, act) 128 } 129 assert.Equal(t, int64(len(keys)), int64(m.resident)) 130 } 131 132 func testSwissMapDeleteRunCore[K infra.OrderedKey](t *testing.T, keys []K) { 133 m := newSwissMap[K, K](uint32(len(keys))) 134 assert.Equal(t, int64(0), m.Len()) 135 for _, key := range keys { 136 m.Put(key, key) 137 } 138 assert.Equal(t, int64(len(keys)), m.Len()) 139 for _, key := range keys { 140 val, err := m.Delete(key) 141 require.NoError(t, err) 142 require.Equal(t, key, val) 143 _, ok := m.Get(key) 144 assert.False(t, ok) 145 } 146 assert.Equal(t, int64(0), m.Len()) 147 // put keys back after deleting them 148 for _, key := range keys { 149 m.Put(key, key) 150 } 151 assert.Equal(t, int64(len(keys)), m.Len()) 152 } 153 154 func testSwissMapClearRunCore[K infra.OrderedKey](t *testing.T, keys []K) { 155 m := newSwissMap[K, int](0) 156 assert.Equal(t, int64(0), m.Len()) 157 for i, key := range keys { 158 m.Put(key, i) 159 } 160 assert.Equal(t, int64(len(keys)), m.Len()) 161 m.Clear() 162 assert.Equal(t, int64(0), m.Len()) 163 for _, key := range keys { 164 _, ok := m.Get(key) 165 assert.False(t, ok) 166 } 167 var calls int 168 m.Foreach(func(i uint64, key K, val int) bool { 169 calls++ 170 return true // continue 171 }) 172 assert.Equal(t, 0, calls) 173 174 var k K 175 for _, g := range m.slots { 176 for i := range g.keys { 177 assert.Equal(t, k, g.keys[i]) 178 assert.Equal(t, 0, g.vals[i]) 179 } 180 } 181 } 182 183 func testSwissMapForeachRunCore[K infra.OrderedKey](t *testing.T, keys []K) { 184 m := newSwissMap[K, int](uint32(len(keys))) 185 for i, key := range keys { 186 m.Put(key, i) 187 } 188 visited := make(map[K]uint, len(keys)) 189 m.Foreach(func(i uint64, k K, v int) bool { 190 visited[k] = 0 191 return true 192 }) 193 if len(keys) == 0 { 194 assert.Equal(t, len(visited), 0) 195 } else { 196 assert.Equal(t, len(visited), len(keys)) 197 } 198 199 for _, k := range keys { 200 visited[k] = 0 201 } 202 m.Foreach(func(i uint64, k K, v int) bool { 203 visited[k]++ 204 return true 205 }) 206 for _, c := range visited { 207 assert.Equal(t, c, uint(1)) 208 } 209 // mutate on iter 210 m.Foreach(func(i uint64, k K, v int) bool { 211 m.Put(k, -v) 212 return true 213 }) 214 for i, key := range keys { 215 act, ok := m.Get(key) 216 assert.True(t, ok) 217 assert.Equal(t, -i, act) 218 } 219 } 220 221 func testSwissMapMigrateFromRunCore[K infra.OrderedKey](t *testing.T, keys []K) { 222 m := newSwissMap[K, int](uint32(len(keys))) 223 _m := make(map[K]int, len(keys)) 224 for i, key := range keys { 225 _m[key] = i 226 } 227 228 err := m.MigrateFrom(_m) 229 require.NoError(t, err) 230 m.Foreach(func(i uint64, k K, v int) bool { 231 val, ok := _m[k] 232 require.True(t, ok) 233 require.Equal(t, val, v) 234 return true 235 }) 236 } 237 238 func testSwissMapRehashRunCore[K infra.OrderedKey](t *testing.T, keys []K) { 239 n := uint32(len(keys)) 240 m := newSwissMap[K, int](n / 10) 241 for i, key := range keys { 242 m.Put(key, i) 243 } 244 for i, key := range keys { 245 act, ok := m.Get(key) 246 assert.True(t, ok) 247 assert.Equal(t, i, act) 248 } 249 } 250 251 func testSwissMapCapacityRunCore[K infra.OrderedKey](t *testing.T, gen func(n int) []K) { 252 caps := []uint32{ 253 1 * maxAvgSlotLoad, 254 2 * maxAvgSlotLoad, 255 3 * maxAvgSlotLoad, 256 4 * maxAvgSlotLoad, 257 5 * maxAvgSlotLoad, 258 10 * maxAvgSlotLoad, 259 25 * maxAvgSlotLoad, 260 50 * maxAvgSlotLoad, 261 100 * maxAvgSlotLoad, 262 } 263 for _, c := range caps { 264 m := newSwissMap[K, K](c) 265 assert.Equal(t, int64(c), m.Cap()) 266 keys := gen(rand.Intn(int(c))) 267 for _, k := range keys { 268 m.Put(k, k) 269 } 270 assert.Equal(t, int64(int(c)-len(keys)), m.Cap()) 271 assert.Equal(t, int64(c), m.Len()+m.Cap()) 272 } 273 } 274 275 func testSwissMapRunCore[K infra.OrderedKey](t *testing.T, keys []K) { 276 // sanity check 277 require.Equal(t, len(keys), len(uniqueKeys(keys)), keys) 278 t.Run("put-get", func(tt *testing.T) { 279 testSwissMapPutRunCore(tt, keys) 280 }) 281 t.Run("put-delete-get-put", func(tt *testing.T) { 282 testSwissMapDeleteRunCore(tt, keys) 283 }) 284 t.Run("clear-foreach", func(tt *testing.T) { 285 testSwissMapClearRunCore(tt, keys) 286 }) 287 t.Run("put-foreach", func(tt *testing.T) { 288 testSwissMapForeachRunCore(tt, keys) 289 }) 290 t.Run("rehash", func(tt *testing.T) { 291 testSwissMapRehashRunCore(tt, keys) 292 }) 293 t.Run("migrate from go native map", func(tt *testing.T) { 294 testSwissMapMigrateFromRunCore(tt, keys) 295 }) 296 } 297 298 func TestSwissMap(t *testing.T) { 299 t.Run("stringKeys=0", func(tt *testing.T) { 300 testSwissMapRunCore[string](tt, genStrKeys(16, 0)) 301 }) 302 t.Run("stringKeys=100", func(tt *testing.T) { 303 testSwissMapRunCore[string](tt, genStrKeys(16, 100)) 304 }) 305 t.Run("stringKeys=1000", func(tt *testing.T) { 306 testSwissMapRunCore[string](tt, genStrKeys(16, 1000)) 307 }) 308 t.Run("stringKeys=10_000", func(tt *testing.T) { 309 testSwissMapRunCore[string](tt, genStrKeys(16, 10_000)) 310 }) 311 t.Run("stringKeys=100_000", func(tt *testing.T) { 312 testSwissMapRunCore[string](tt, genStrKeys(16, 100_000)) 313 }) 314 t.Run("stringKeys-cap", func(tt *testing.T) { 315 testSwissMapCapacityRunCore(tt, func(n int) []string { 316 return genStrKeys(16, n) 317 }) 318 }) 319 320 t.Run("uint64Keys=0", func(tt *testing.T) { 321 testSwissMapRunCore[uint64](tt, genUint64Keys(0)) 322 }) 323 t.Run("uint64Keys=100", func(tt *testing.T) { 324 testSwissMapRunCore[uint64](tt, genUint64Keys(100)) 325 }) 326 t.Run("uint64Keys=1000", func(tt *testing.T) { 327 testSwissMapRunCore[uint64](tt, genUint64Keys(1000)) 328 }) 329 t.Run("uint64Keys=10_000", func(tt *testing.T) { 330 testSwissMapRunCore[uint64](tt, genUint64Keys(10_000)) 331 }) 332 t.Run("uint64Keys=100_000", func(tt *testing.T) { 333 testSwissMapRunCore[uint64](tt, genUint64Keys(100_000)) 334 }) 335 t.Run("uint64Keys-cap", func(tt *testing.T) { 336 testSwissMapCapacityRunCore(tt, genUint64Keys) 337 }) 338 339 t.Run("float64Keys=0", func(tt *testing.T) { 340 testSwissMapRunCore[float64](tt, genFloat64Keys(0)) 341 }) 342 t.Run("float64Keys=100", func(tt *testing.T) { 343 testSwissMapRunCore[float64](tt, genFloat64Keys(100)) 344 }) 345 t.Run("float64Keys=1000", func(tt *testing.T) { 346 testSwissMapRunCore[float64](tt, genFloat64Keys(1000)) 347 }) 348 t.Run("float64Keys=10_000", func(tt *testing.T) { 349 testSwissMapRunCore[float64](tt, genFloat64Keys(10_000)) 350 }) 351 t.Run("float64Keys=100_000", func(tt *testing.T) { 352 testSwissMapRunCore[float64](tt, genFloat64Keys(100_000)) 353 }) 354 t.Run("float64Keys-cap", func(tt *testing.T) { 355 testSwissMapCapacityRunCore(tt, genFloat64Keys) 356 }) 357 } 358 359 func fuzzStringSwissMap(t *testing.T, strKeyLen, count int, initMapCap uint32) { 360 const limit = 1024 * 1024 361 if count > limit || initMapCap > limit { 362 t.Skip() 363 } 364 m := newSwissMap[string, int](initMapCap) 365 if count == 0 { 366 return 367 } 368 369 keys := genStrKeys(strKeyLen, count) 370 standard := make(map[string]int, initMapCap) 371 for i, k := range keys { 372 m.Put(k, i) 373 standard[k] = i 374 } 375 assert.Equal(t, int64(len(standard)), m.Len()) 376 377 for k, exp := range standard { 378 act, ok := m.Get(k) 379 assert.True(t, ok) 380 assert.Equal(t, exp, act) 381 } 382 for _, k := range keys { 383 _, ok := standard[k] 384 assert.True(t, ok) 385 _, exists := m.Get(k) 386 assert.True(t, exists) 387 } 388 389 deletes := keys[:count/2] 390 for _, k := range deletes { 391 delete(standard, k) 392 m.Delete(k) 393 } 394 assert.Equal(t, int64(len(standard)), m.Len()) 395 396 for _, k := range deletes { 397 _, exists := m.Get(k) 398 assert.False(t, exists) 399 } 400 for k, exp := range standard { 401 act, ok := m.Get(k) 402 assert.True(t, ok) 403 assert.Equal(t, exp, act) 404 } 405 } 406 407 func FuzzStringSwissMap(f *testing.F) { 408 f.Add(1, 50, uint32(14)) 409 f.Add(2, 1, uint32(1)) 410 f.Add(2, 14, uint32(14)) 411 f.Add(2, 15, uint32(14)) 412 f.Add(2, 100, uint32(25)) 413 f.Add(2, 1000, uint32(25)) 414 f.Add(8, 1, uint32(0)) 415 f.Add(8, 1, uint32(1)) 416 f.Add(8, 14, uint32(14)) 417 f.Add(8, 15, uint32(14)) 418 f.Add(8, 100, uint32(25)) 419 f.Add(8, 1000, uint32(25)) 420 f.Add(16, 100_000, uint32(10_000)) 421 f.Fuzz(func(t *testing.T, strKeyLen, count int, initMapCap uint32) { 422 fuzzStringSwissMap(t, strKeyLen, count, initMapCap) 423 }) 424 } 425 426 func TestMemFootprint(t *testing.T) { 427 var samples []float64 428 for n := 10; n <= 10_000; n += 10 { 429 b1 := testing.Benchmark(func(b *testing.B) { 430 // max load factor 7/8 => 14/16 431 m := newSwissMap[int, int](uint32(n)) 432 require.NotNil(b, m) 433 }) 434 b2 := testing.Benchmark(func(b *testing.B) { 435 // max load factor 6.5/8 436 m := make(map[int]int, n) 437 require.NotNil(b, m) 438 }) 439 x := float64(b1.MemBytes) / float64(b2.MemBytes) 440 samples = append(samples, x) 441 } 442 t.Logf("mean size ratio: %.3f", func() float64 { 443 var sum float64 444 for _, x := range samples { 445 sum += x 446 } 447 return sum / float64(len(samples)) 448 }()) 449 } 450 451 func BenchmarkStringSwissMaps(b *testing.B) { 452 const strKeyLen = 8 453 sizes := []int{16, 128, 1024, 8192, 131072} 454 for _, n := range sizes { 455 b.Run("n="+strconv.Itoa(n), func(bb *testing.B) { 456 keys := genStrKeys(strKeyLen, n) 457 n := uint32(len(keys)) 458 mod := n - 1 // power of 2 fast modulus 459 require.Equal(bb, 1, bits.OnesCount32(n)) 460 m := newSwissMap[string, string](n) 461 for _, k := range keys { 462 m.Put(k, k) 463 } 464 bb.ResetTimer() 465 var ok bool 466 for i := 0; i < b.N; i++ { 467 _, ok = m.Get(keys[uint32(i)&mod]) 468 require.True(b, ok) 469 } 470 bb.ReportAllocs() 471 }) 472 } 473 } 474 475 func TestStringSwissMapsWriteOnly(t *testing.T) { 476 const strKeyLen = 8 477 sizes := []int{16, 128, 1024, 8192, 131072, 1<<20, 1<<24} 478 for _, n := range sizes { 479 t.Run("SwissMap n="+strconv.Itoa(n), func(tt *testing.T) { 480 keys := genStrKeys(strKeyLen, n) 481 n := uint32(len(keys)) 482 require.Equal(tt, 1, bits.OnesCount32(n)) 483 m := newSwissMap[string, string](n) 484 var count atomic.Uint32 485 for _, k := range keys { 486 err := m.Put(k, k) 487 require.NoError(tt, err) 488 count.Add(1) 489 } 490 for _, k := range keys { 491 v, ok := m.Get(k) 492 require.True(tt, ok) 493 require.Equal(tt, k, v) 494 } 495 }) 496 t.Run("Map n="+strconv.Itoa(n), func(tt *testing.T) { 497 keys := genStrKeys(strKeyLen, n) 498 n := uint32(len(keys)) 499 require.Equal(tt, 1, bits.OnesCount32(n)) 500 m := make(map[string]string, n) 501 var count atomic.Uint32 502 for _, k := range keys { 503 m[k] = k 504 count.Add(1) 505 } 506 for _, k := range keys { 507 v, ok := m[k] 508 require.True(tt, ok) 509 require.Equal(tt, k, v) 510 } 511 }) 512 } 513 }