github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/kv/thread_safe_map_test.go (about) 1 package kv 2 3 import ( 4 "fmt" 5 "math/bits" 6 randv2 "math/rand/v2" 7 "strconv" 8 "sync" 9 "sync/atomic" 10 "testing" 11 12 "github.com/stretchr/testify/require" 13 ) 14 15 func TestThreadSafeMap_SimpleCRUD(t *testing.T) { 16 keys := genStrKeys(8, 10000) 17 vals := make([]int, 0, len(keys)) 18 m := make(map[string]int, len(keys)) 19 _m := NewThreadSafeMap[string, int](WithThreadSafeMapInitCap[string, int](10_000), 20 WithThreadSafeMapCloseableItemCheck[string, int](), 21 ) 22 for i, key := range keys { 23 m[key] = i 24 vals = append(vals, i) 25 } 26 _m.Replace(m) 27 28 _keys := _m.ListKeys() 29 require.Equal(t, len(keys), len(_keys)) 30 require.ElementsMatch(t, keys, _keys) 31 32 _vals := _m.ListValues() 33 require.ElementsMatch(t, vals, _vals) 34 35 i := 1001 36 res, exists := _m.Get(keys[i]) 37 require.True(t, exists) 38 require.Equal(t, i, res) 39 40 res, err := _m.Delete(keys[i]) 41 require.NoError(t, err) 42 require.Equal(t, i, res) 43 44 err = _m.AddOrUpdate(keys[i], i) 45 require.NoError(t, err) 46 47 _keys = _m.ListKeys() 48 require.Equal(t, len(keys), len(_keys)) 49 require.ElementsMatch(t, keys, _keys) 50 51 _vals = _m.ListValues() 52 require.ElementsMatch(t, vals, _vals) 53 54 err = _m.Purge() 55 require.NoError(t, err) 56 } 57 58 func BenchmarkStringThreadSafeMap(b *testing.B) { 59 const strKeyLen = 8 60 sizes := []int{16, 128, 1024, 8192, 131072, 1 << 20} 61 for _, n := range sizes { 62 b.Run("ThreadSafeMap n="+strconv.Itoa(n), func(bb *testing.B) { 63 keys := genStrKeys(strKeyLen, n) 64 n := uint32(len(keys)) 65 mod := n - 1 // power of 2 fast modulus 66 require.Equal(bb, 1, bits.OnesCount32(n)) 67 m := NewThreadSafeMap[string, string](WithThreadSafeMapInitCap[string, string](n)) 68 bb.ResetTimer() 69 for _, k := range keys { 70 _ = m.AddOrUpdate(k, k) 71 } 72 var ok bool 73 for i := 0; i < b.N; i++ { 74 _, ok = m.Get(keys[uint32(i)&mod]) 75 require.True(b, ok) 76 } 77 bb.ReportAllocs() 78 }) 79 b.Run("SyncMap n="+strconv.Itoa(n), func(bb *testing.B) { 80 keys := genStrKeys(strKeyLen, n) 81 n := uint32(len(keys)) 82 mod := n - 1 // power of 2 fast modulus 83 require.Equal(bb, 1, bits.OnesCount32(n)) 84 m := sync.Map{} 85 bb.ResetTimer() 86 for _, k := range keys { 87 m.Store(k, k) 88 } 89 var ok bool 90 for i := 0; i < b.N; i++ { 91 _, ok = m.Load(keys[uint32(i)&mod]) 92 require.True(b, ok) 93 } 94 bb.ReportAllocs() 95 }) 96 } 97 } 98 99 func BenchmarkThreadSafeMapReadWrite(b *testing.B) { 100 value := []byte(`abc`) 101 for i := 0; i <= 10; i++ { 102 b.Run(fmt.Sprintf("ThreadSafeMap frac_%d", i), func(bb *testing.B) { 103 readFrac := float32(i) / 10.0 104 tsm := NewThreadSafeMap[int, []byte]( 105 WithThreadSafeMapInitCap[int, []byte](4096), 106 ) 107 bb.ResetTimer() 108 count := atomic.Int32{} 109 bb.RunParallel(func(pb *testing.PB) { 110 for pb.Next() { 111 if randv2.Float32() < readFrac { 112 v, exists := tsm.Get(randv2.Int()) 113 if exists && v != nil { 114 count.Add(1) 115 } 116 } else { 117 err := tsm.AddOrUpdate(randv2.Int(), value) 118 require.NoError(bb, err) 119 } 120 } 121 }) 122 }) 123 b.Run(fmt.Sprintf("SyncMap frac_%d", i), func(bb *testing.B) { 124 readFrac := float32(i) / 10.0 125 tsm := sync.Map{} 126 bb.ResetTimer() 127 count := atomic.Int32{} 128 bb.RunParallel(func(pb *testing.PB) { 129 for pb.Next() { 130 if randv2.Float32() < readFrac { 131 v, exists := tsm.Load(randv2.Int()) 132 if exists && v != nil { 133 count.Add(1) 134 } 135 } else { 136 tsm.Store(randv2.Int(), value) 137 } 138 } 139 }) 140 }) 141 } 142 } 143 144 func BenchmarkStringThreadSafeMap_DataRace(b *testing.B) { 145 const strKeyLen = 8 146 sizes := []int{16, 128, 512, 1024, 8192, 131072, 1 << 19} 147 for _, n := range sizes { 148 b.Run("ThreadSafeMap n="+strconv.Itoa(n), func(bb *testing.B) { 149 keys := genStrKeys(strKeyLen, n) 150 n := uint32(len(keys)) 151 mod := n - 1 // power of 2 fast modulus 152 require.Equal(bb, 1, bits.OnesCount32(n)) 153 m := NewThreadSafeMap[string, string](WithThreadSafeMapInitCap[string, string](n)) 154 channels := make([]chan string, 4) 155 for i := 0; i < len(channels); i++ { 156 channels[i] = make(chan string) 157 } 158 wg := sync.WaitGroup{} 159 wg.Add(4) 160 bb.ResetTimer() 161 go func() { 162 for k := range channels[0] { 163 _ = m.AddOrUpdate(k, k) 164 } 165 wg.Done() 166 }() 167 go func() { 168 for k := range channels[1] { 169 _ = m.AddOrUpdate(k, k) 170 } 171 wg.Done() 172 }() 173 go func() { 174 for k := range channels[2] { 175 _ = m.AddOrUpdate(k, k) 176 } 177 wg.Done() 178 }() 179 go func() { 180 for k := range channels[3] { 181 _ = m.AddOrUpdate(k, k) 182 } 183 wg.Done() 184 }() 185 for i, k := range keys { 186 channels[i%4] <- k 187 } 188 for i := 0; i < 4; i++ { 189 close(channels[i]) 190 } 191 wg.Wait() 192 var ok bool 193 for i := 0; i < b.N; i++ { 194 _, ok = m.Get(keys[uint32(i)&mod]) 195 require.True(b, ok) 196 } 197 bb.ReportAllocs() 198 }) 199 b.Run("syncMap n="+strconv.Itoa(n), func(bb *testing.B) { 200 keys := genStrKeys(strKeyLen, n) 201 n := uint32(len(keys)) 202 mod := n - 1 // power of 2 fast modulus 203 require.Equal(bb, 1, bits.OnesCount32(n)) 204 m := sync.Map{} 205 channels := make([]chan string, 4) 206 for i := 0; i < len(channels); i++ { 207 channels[i] = make(chan string) 208 } 209 wg := sync.WaitGroup{} 210 wg.Add(4) 211 bb.ResetTimer() 212 go func() { 213 for k := range channels[0] { 214 m.Store(k, k) 215 } 216 wg.Done() 217 }() 218 go func() { 219 for k := range channels[1] { 220 m.Store(k, k) 221 } 222 wg.Done() 223 }() 224 go func() { 225 for k := range channels[2] { 226 m.Store(k, k) 227 } 228 wg.Done() 229 }() 230 go func() { 231 for k := range channels[3] { 232 m.Store(k, k) 233 } 234 wg.Done() 235 }() 236 for i, k := range keys { 237 channels[i%4] <- k 238 } 239 for i := 0; i < 4; i++ { 240 close(channels[i]) 241 } 242 wg.Wait() 243 var ok bool 244 for i := 0; i < b.N; i++ { 245 _, ok = m.Load(keys[uint32(i)&mod]) 246 require.True(b, ok) 247 } 248 bb.ReportAllocs() 249 }) 250 } 251 }