github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/bench/microbenchmarks/map/main.go (about) 1 // Package main 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package main 6 7 import ( 8 "flag" 9 "fmt" 10 "os" 11 "sync" 12 "time" 13 14 "github.com/NVIDIA/aistore/cmn/cos" 15 ) 16 17 // =============Preamble================ 18 // This benchmark tests the performance of regular maps versus sync.Maps focusing on the 19 // primitive operations of get, put, and delete while under contention. For the use case of 20 // AIStore, we want the read times to be as fast as possible. 21 // 22 // The design of the benchmark is as follows: 23 // 1. Fill the map to a max size (default 1000000) 24 // 2. Randomly get X (default 200000) elements from the map where p% (default 10%) of them will be a random store 25 // 3. Randomly delete (max size - min size) elements from the map 26 // All of these operations will happen concurrently with step 2 having the option to spawn more 27 // go routines (default 8). Additionally, in all cases, the elements being read from the maps, 28 // put into the maps, and deleted from the maps will be the same between the two maps. 29 // 30 // Example of invocation: 31 // go run main.go -numread 300000 -maxsize 1000000 -minsize 50000 -putpct 10 -workers 8 32 // go run main.go -numread 100000 -maxsize 1000000 -minsize 50000 -putpct 20 -workers 20 -shrink=false (no deletion) 33 34 // ===========Results=================== 35 // From the results of the benchmark, sync.Map does appear to out perform regular maps for read operations 36 // when there is contention from multiple go routines. The performance of sync.Map for put and delete operations are usually slower than 37 // regular maps, but if we expect > 80% of the requests to be reads, sync.Map will be faster than regular maps. 38 // Overall, if we expect that the maps will have much higher read requests than put/delete requests, sync.Maps are 39 // the better option. 40 41 type ( 42 benchMap interface { 43 get(int) int 44 put(node) 45 delete(int) 46 mapType() string 47 } 48 node struct { 49 key, val int 50 } 51 52 // Maps 53 regMap struct { 54 m map[int]int 55 // Regular map RWMutex 56 mu sync.RWMutex 57 } 58 59 syncMap struct { 60 m sync.Map 61 } 62 params struct { 63 maxSize int // Max size of map 64 minSize int // Shrink size of map 65 numread int // Number of random reads 66 iterations int // Number of iterations to run 67 putpct int // Percentage of puts 68 workers int // Number of Read/Write Goroutines 69 doShrink bool // Enable shrinking (Deleting) of keys in map 70 } 71 ) 72 73 var ( 74 // Params 75 runParams params 76 flagUsage bool 77 78 nodeList []node 79 readList [][]int 80 delList [][]int 81 ) 82 83 // =================sync.Map=================== 84 func newSyncMap() *syncMap { 85 return &syncMap{} 86 } 87 88 func (m *syncMap) get(key int) int { 89 val, _ := m.m.Load(key) 90 if val == nil { 91 return 0 92 } 93 return val.(int) 94 } 95 96 func (m *syncMap) put(n node) { 97 m.m.Store(n.key, n.val) 98 } 99 100 func (m *syncMap) delete(key int) { 101 m.m.Delete(key) 102 } 103 104 func (*syncMap) mapType() string { 105 return "Sync.Map" 106 } 107 108 // =================Regular Map=================== 109 func newRegMap() *regMap { 110 return ®Map{make(map[int]int), sync.RWMutex{}} 111 } 112 113 func (m *regMap) get(key int) int { 114 m.mu.RLock() 115 val := m.m[key] 116 m.mu.RUnlock() 117 return val 118 } 119 120 func (m *regMap) put(n node) { 121 m.mu.Lock() 122 m.m[n.key] = n.val 123 m.mu.Unlock() 124 } 125 126 func (m *regMap) delete(key int) { 127 m.mu.Lock() 128 delete(m.m, key) 129 m.mu.Unlock() 130 } 131 132 func (*regMap) mapType() string { 133 return "Reg.Map" 134 } 135 136 func parseCmdLine() (p params, err error) { 137 f := flag.NewFlagSet(os.Args[0], flag.ExitOnError) // Discard flags of imported packages 138 139 // Command line options 140 f.BoolVar(&flagUsage, "usage", false, "Show extensive help menu with examples and exit") 141 f.BoolVar(&p.doShrink, "shrink", true, "Enable shrink (deletion) of map keys") 142 f.IntVar(&p.maxSize, "maxsize", 1000000, "Maximum size of map") 143 f.IntVar(&p.minSize, "minsize", 500000, "Shrink size of map") 144 f.IntVar(&p.numread, "numread", 300000, "Number of random reads") 145 f.IntVar(&p.iterations, "iter", 10, "Number of iterations to run") 146 f.IntVar(&p.putpct, "putpct", 10, "Percentage of puts") 147 f.IntVar(&p.workers, "workers", 8, "Number of Read/Write go-routines") 148 149 f.Parse(os.Args[1:]) 150 151 if len(os.Args[1:]) == 0 { 152 fmt.Println("Flags: ") 153 f.PrintDefaults() 154 fmt.Println() 155 os.Exit(0) 156 } 157 158 os.Args = []string{os.Args[0]} 159 flag.Parse() // Called so that imported packages don't compain 160 161 if p.minSize > p.maxSize { 162 return params{}, fmt.Errorf("minsize %d greater than maxsize %d", p.minSize, p.maxSize) 163 } 164 165 if p.putpct < 0 || p.putpct > 100 { 166 return params{}, fmt.Errorf("invalid putpct: %d", p.putpct) 167 } 168 169 return 170 } 171 172 func init() { 173 var err error 174 runParams, err = parseCmdLine() 175 if err != nil { 176 fmt.Fprintln(os.Stdout, "err: "+err.Error()) 177 os.Exit(2) 178 } 179 180 fmt.Printf("Map size: %d\t Min size: %d\t Read count: %d\t Iterations: %d\t Put Pct: %d\t Workers: %d\n\n", 181 runParams.maxSize, runParams.minSize, runParams.numread, runParams.iterations, runParams.putpct, runParams.workers) 182 nodeList = createNodeList(runParams.maxSize) 183 readList = createReadDelList(nodeList, runParams.numread, runParams.iterations) 184 delList = createReadDelList(nodeList, runParams.maxSize-runParams.minSize, runParams.iterations) 185 } 186 187 /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ 188 func randInt(r int) int { 189 r ^= r << 13 190 r ^= r >> 17 191 r ^= r << 5 192 return r & 0x7fffffff 193 } 194 195 // Create a node struct where key and value are the same (it should not matter) 196 func createNodeList(size int) (nodeList []node) { 197 nodeList = make([]node, 0, size) 198 for i := range size { 199 nodeList = append(nodeList, node{key: i, val: randInt(i)}) 200 } 201 return 202 } 203 204 // Creates a list of keys (int) 205 func createList(nodeList []node, size int) (keys []int) { 206 nodeLen := len(nodeList) 207 keys = make([]int, 0, size) 208 209 for range size { 210 keys = append(keys, cos.NowRand().Intn(nodeLen)) 211 } 212 return 213 } 214 215 // Creates a list of list of keys 216 func createReadDelList(nodeList []node, size, iterations int) (keysList [][]int) { 217 wg := &sync.WaitGroup{} 218 219 keysList = make([][]int, iterations) 220 wg.Add(iterations) 221 for i := range iterations { 222 go func(index int) { 223 keysList[index] = createList(nodeList, size) 224 wg.Done() 225 }(i) 226 } 227 wg.Wait() 228 return 229 } 230 231 func fill(m benchMap, nodeList []node) time.Duration { 232 start := time.Now() 233 for _, ele := range nodeList { 234 m.put(ele) 235 } 236 return time.Since(start) 237 } 238 239 func shrink(m benchMap, keys []int) time.Duration { 240 start := time.Now() 241 for _, ele := range keys { 242 m.delete(ele) 243 } 244 return time.Since(start) 245 } 246 247 func readNWrite(m benchMap, threshold int, keys []int, dur chan time.Duration) { 248 start := time.Now() 249 for _, key := range keys { 250 // Randomly have some puts 251 if key%100 >= (100-threshold) || key%100 <= (-100+threshold) { 252 m.put(node{key: key, val: key}) 253 } else { 254 m.get(key) 255 } 256 } 257 dur <- time.Since(start) 258 } 259 260 // Benchmark 261 // 1. Fill map 262 // 2. Concurrently run X go routines that reads and occasionally writes 263 // 3. Shrink 264 func bench(m benchMap, nodeList []node, readList, delList [][]int, iterations, threshold, workers int, doShrink bool) { 265 var ( 266 wg = &sync.WaitGroup{} 267 fillDur time.Duration 268 shrinkDur time.Duration 269 readWriteDur time.Duration 270 totDur time.Duration 271 272 totFil time.Duration 273 totRW time.Duration 274 totShrink time.Duration 275 totTot time.Duration 276 ) 277 fmt.Printf("Fill, Read + Random Write and Shrink with %s\n", m.mapType()) 278 279 for i := range iterations { 280 start := time.Now() 281 readDurCh := make(chan time.Duration, workers) 282 wg.Add(1) 283 go func() { 284 fillDur = fill(m, nodeList) 285 wg.Done() 286 }() 287 288 for range workers { 289 wg.Add(1) 290 go func(idx int) { 291 readNWrite(m, threshold, readList[idx], readDurCh) 292 wg.Done() 293 }(i) 294 } 295 296 if doShrink { 297 wg.Add(1) 298 go func(idx int) { 299 shrinkDur = shrink(m, delList[idx]) 300 wg.Done() 301 }(i) 302 } 303 wg.Wait() 304 close(readDurCh) 305 for dur := range readDurCh { 306 readWriteDur += dur 307 } 308 totDur = time.Since(start) 309 310 averageRW := time.Duration(int(readWriteDur) / workers) 311 fmt.Printf("%s | Fill: %s\t Read+Write: %s\t Shrink: %s\t Total: %s\n", 312 m.mapType(), fillDur, averageRW, shrinkDur, totDur) 313 readWriteDur = 0 314 totFil += fillDur 315 totRW += averageRW 316 totShrink += shrinkDur 317 totTot += totDur 318 } 319 fmt.Println() 320 fmt.Printf("%s | Avg. Fill: %s\t Avg. Read+Write: %s\t Avg. Shrink: %s\t Avg. Total: %s\n", 321 m.mapType(), 322 time.Duration(int(totFil)/iterations), 323 time.Duration(int(totRW)/iterations), 324 time.Duration(int(totShrink)/iterations), 325 time.Duration(int(totTot)/iterations)) 326 fmt.Println() 327 } 328 329 func main() { 330 sMap := newSyncMap() 331 rMap := newRegMap() 332 333 bench(sMap, nodeList, readList, delList, runParams.iterations, runParams.putpct, runParams.workers, runParams.doShrink) 334 bench(rMap, nodeList, readList, delList, runParams.iterations, runParams.putpct, runParams.workers, runParams.doShrink) 335 }