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  }